Hakan Baba
Hakan Baba

Reputation: 2035

How to test whether a function was waited on, not only called?

Consider testing the following simplified function

const functionToBeTested = async (val) => {
    await otherModule.otherFunction(val/2);
}

In my jest test I want to make sure that the otherModule.otherFunction is not only called but also waited on. In other words, I want to write a test that will fail if someone removes the await from in front of the otherFunction call.

I have so far this

test('should wait on otherFunction', () => {
   await functionToBeTested(6) 
   expect(otherModule.otherFunction).toHaveBeenCalledWith(3);  
}

But the expect(otherModule.otherFunction).toHaveBeenCalledWith(3); check does not verify that functionToBeTested has waited on otherFunction.

Upvotes: 0

Views: 61

Answers (2)

Sergio Mazzoleni
Sergio Mazzoleni

Reputation: 1598

If you cannot check against otherModule.otherFunction resolved value or on any side-effects, there is no need to test wether it resolves.

Otherwise, removing await in following examples will cause the tests to fail.

describe('check for side effect', () => {
    let sideEffect = false;

    const otherModule = {
        otherFunction: x =>
            new Promise(resolve => {
                setTimeout(() => {
                    sideEffect = true;
                    resolve();
                }, 0);
            }),
    };

    const functionToBeTested = async val => {
        await otherModule.otherFunction(val / 2);
    };

    test('should wait on otherFunction', async () => {
        const spy = jest.spyOn(otherModule, 'otherFunction');

        await expect(functionToBeTested(6)).resolves.toBeUndefined();
        expect(spy).toHaveBeenCalledWith(3);
        expect(sideEffect).toBe(true);
    });
});

describe('check returned value', () => {
    const otherModule = {
        otherFunction: x =>
            new Promise(resolve => {
                setTimeout(() => {
                    resolve('hello');
                }, 0);
            }),
    };

    const functionToBeTested = async val => {
        const res = await otherModule.otherFunction(val / 2);
        return `*** ${res} ***`;
    };

    test('should wait on otherFunction', async () => {
        const spy = jest.spyOn(otherModule, 'otherFunction');

        const promise = functionToBeTested(6);
        expect(spy).toHaveBeenCalledWith(3);
        await expect(promise).resolves.toBe('*** hello ***');
    });
});

Upvotes: 0

Nicholas Tower
Nicholas Tower

Reputation: 85012

Here's what i came up with:

const delay = duration => new Promise(resolve => setTimeout(resolve, duration));

test('should wait on otherFunction', async () => {
  let resolve;
  const mockPromise = new Promise((res) => {resolve = res;});
  otherModule.otherFunction.mockReturnValue(mockPromise);
  const resolution = jest.fn();

  functionToBeTested(6).then(resolution);

  expect(otherModule.otherFunction).toHaveBeenCalledWith(3);
  await delay(0);
  expect(resolution).not.toHaveBeenCalled();
  resolve();
  await delay(0);
  expect(resolution).toHaveBeenCalled();
}

So, i mock otherFunction to return a promise which starts unresolved, but i can resolve it at will during the test. Then i call the function i want to test, and give it a callback for when its complete.

I then want to assert that it did not call the callback, but since promise resolution is always asynchronous i need to add in a timeout 0 to give the promise a chance to resolve. I chose to do this with a promis-ified version of setTimeout.

And finally, i resolve the mockPromise, do a timeout 0 (again, to make sure the promise gets a chance to call its callbacks), and assert that now the resolution has been called.

Upvotes: 1

Related Questions