2

Good Day Everyone,

I am quite new to testing frameworks Jasmine. We have a TS project setup and I am trying to test a function consisting of setTimeout, but it keeps failing. I am trying to use Clock

One Important point I noticed is that I am using babel-loader as soon as I change the webpack configuration to ts-loader. The test case doesn't fail. (Don't know Why ‍♀️). I have checked multiple times but no luck.

UPDATE I figured out why the test cases are failing but I have no idea why is it happening.

The configurations of babel and ts-loader are correct I just changed the setTimeout to window.setTimeout(in babel-loader) repository and now the test cases are executing successfully‍♀️. This was just a wild guess from Stack Overflow Link.

enter image description here

I added a console statement of setTimeout and window.setTimeout and found that the definitions of both functions are very different. window.setTimeout has a definition(Green circle in the screenshot) that is the same as after we install Jasmine.clock install. ie. HERE But setTimeout definition(Red circle in the screenshot) is very different(below).

function (/* ...args */) {
    return fn.apply(that, arguments);
  }

I tried doing the same in ts-loader repository but here setTimeout and window.setTimeout definitions are the same.

Code:

hideToast() {
        setTimeout(() => {
            this.showToast = false;
        }, 5000);
    }

Test Spec:

beforeEach(() => {
        // Sometimes calling install() will fail for some reason,
        // but calling uninstall() first will make it work
        jasmine.clock().uninstall();
        jasmine.clock().install();
    });

afterEach(() => {
        jasmine.clock().uninstall();
});

it('should hide toast', () => {
        const obj: Car = new Car();
        obj.showToast = true; // This value should change after timeout

        obj.hideToast();      // Call the component method that turns the showToast value as false

        jasmine.clock().tick(5000);
        expect(obj.showToast).toBeFalsy();  // Then executes this
    });

Any suggestion would be helpful.

With babel-loader(Test case fail's) Screenshot(Branch = "main"):

https://github.com/dollysingh3192/ts-babel-template

With ts-loader(Test cases passed) Screenshot(Branch = "tsloader"):

https://github.com/dollysingh3192/ts-babel-template/tree/tsloader
Dolly
  • 2,213
  • 16
  • 38
  • This problem is resolved and detailed answer is https://stackoverflow.com/a/66349539/10102695 posted by me. Hope it helps to someone facing similar problem. – Dolly Mar 01 '21 at 05:29

1 Answers1

-1

Example with clock() using a promise that we resolve straight away:

import { Car } from './car';

describe('Car', () => {
    beforeEach(() => {
        jasmine.clock().uninstall(); // you probably don't need this line
        jasmine.clock().install();
    });

    afterEach(() => {
        jasmine.clock().uninstall();
    });

    it('should hide toast', () => {
        const obj: Car = new Car();
        obj.showToast = true; // also here, it's redundant, as set to true in constructor
        obj.hideToast();
        Promise.resolve().then(() => {
            jasmine.clock().tick(5000);
            expect(obj.showToast).toBeFalsy();
        });
    });
});

Other ideas on how to do it in this github issue

But let's forget about the simple (and best solution) for a moment and dig a big into the issue. Without the Clock class, we would have to use the done() callback. In that case, we would have two solutions:

1 - change jasmine.DEFAULT_TIMEOUT_INTERVAL, to be at least 1ms higher than your timeout:

import { Car } from './car';

describe('Car', () => {
    beforeEach(() => {
        jasmine.DEFAULT_TIMEOUT_INTERVAL = 5001;
    });

    it('should hide toast', (done) => {
        const obj: Car = new Car();
        obj.hideToast();
        setTimeout(() => {
            expect(obj.showToast).toBeFalsy();
            done();
        }, 5000)
    });
});

This works with the done() callback (that might be used while working with async tasks) and by overriding the jasmine.DEFAULT_TIMEOUT_INTERVAL.

2 - Lower your timeout - by 1 millisecond (yes, 1ms is enough):

import {Car} from './car';

describe('Car', () => {
    it('go() works', () => {
        const car: Car = new Car();
        const returnValue = car.go('vroom');
        expect(returnValue).toEqual('vroom');
    });

    it('should hide toast', (done) => {
        const obj: Car = new Car();
        obj.hideToast();
        setTimeout(() => {
            expect(obj.showToast).toEqual(false);
            done();
        }, 4999);
    });
});

Same as before, done callback, but this time we finish 1 millisecond before the Jasmine default timeout and the test will pass (obviously, we need to do it in the class and in the test).

According to the Jasmine docs: jasmine docs

GBra 4.669
  • 1,512
  • 3
  • 11
  • 23
  • finally added a working example with the clock() – GBra 4.669 Jan 22 '21 at 21:28
  • Thanks for the solution. But why in the spec you have used ```Promise.resolve()``` and without using it the spec fails. Could you please through some light on it? – Dolly Jan 27 '21 at 08:34
  • The static `Promise.resolve` function returns a Promise that is resolved. Since it's already resolved, we can skip the reject (since a promise can be either resolved or rejected). `Reject` is used to trigger error handling logic linked to a result of a promise (e.g fetching something for a server that might fail for multiple reasons). If the only way your code would fail is due to an exception, JS will handle the exception and throw the error, so reject would be just redudant, in most cases. Where the promise can actually reject (or if you are unsure), please use reject. – GBra 4.669 Jan 27 '21 at 09:18
  • That's the definition on ```Promise.resolve``` but It is still unclear why we need it in the test case where it should work without them. Like explained https://stackoverflow.com/a/58957018/10102695 and https://www.freecodecamp.org/news/jasmine-unit-testing-tutorial-4e757c2cbf42/ Also, if you change the loader ```ts-loader``` in webpack. The test which I wrote get pass. – Dolly Jan 27 '21 at 11:05
  • It works without promise or `done()` in the question that you linked because they are using Angular. When you test with Angular, you are not only using jasmine, but the Angular framework on top of that. They are testing with a `beforeEach()` lifecycle that is wrapped by `async` or `waitForAsync()` methods that will wait that test automatically completes every async task before finishing the test. Since you are using only Jasmine, you need to wrap it/make it async by yourself. Otherwise it won't wait for the clock to tick, but it will just fail the test. – GBra 4.669 Jan 27 '21 at 13:36
  • regarding the ts-loader, not sure about it. If you think it's worth to be tested, please provide the "updated" version. – GBra 4.669 Jan 27 '21 at 13:37
  • Here is the new github branch with ```tsloader``` https://github.com/dollysingh3192/ts-babel-template/tree/tsloader In this branch all test's passed without ```Promise.resolve()``` – Dolly Jan 27 '21 at 13:49
  • Also, I checked the code of ```Promise.resolve().then(() => { jasmine.clock().tick(5000); expect(obj.showToast).toBeFalsy(); });``` is not working as aspected i.e after .tick(5000) ```obj.showToast``` should be falsy, right but if you update the spec to ```Promise.resolve().then(() => { jasmine.clock().tick(5000); expect(obj.showToast).toBeTruthy(); });``` still the test case's are passing (but instead it should fail here). Please check – Dolly Jan 27 '21 at 18:30
  • Hey @Dolly, I've just seen your question and not that I know the solution, but it seems to me, that if you're using setTimeout with 5000 milliseconds, and taken into consideration, that Javascript does not guarantee the time of execution, I'd test with 6000 milliseconds or something like that. It could be enough to have 1 millisecond more for the test on some machines, not enough on others. – Janos Vinceller Jan 29 '21 at 09:20
  • Hey @JanosVinceller I tried but nothing works this is https://github.com/dollysingh3192/ts-babel-template/blob/tsloader/src/car.ts file which contains function and this is https://github.com/dollysingh3192/ts-babel-template/blob/tsloader/src/car.spec.ts that contain spec. "main" branch on Github. It would be awesome if you could take a look. – Dolly Jan 29 '21 at 09:45
  • I'm sorry, I won't have the time for this. But let me give you this: do a `jasmine.clock().mockDate();` after the `jasmine.clock().install();`. Maybe that helps. Also I'd reduce the timeouts to 500ms and 600ms. Not sure though – Janos Vinceller Jan 29 '21 at 12:22