#testing value of asynchronous function using spyOn

25 messages · Page 1 of 1 (latest)

solemn aspen
#

Hi, I know it doesn't recommended to jest.spyOn on a function that been mocked but i'm trying to check that every call in a service method is called and returns a specific value. i'm getting:
● Crop Service › update › successfull call › should be called 1 time and with CropType id and return a croptype entity

expect(received).resolves.toHaveReturnedWith()

Matcher error: received value must be a promise

Received has type:  function
Received has value: [Function mockConstructor]

  174 |                 expect(spyConnection).toBeCalledTimes(1);
  175 |                 expect(spyConnection).toBeCalledWith(updateCrop.typeId);

176 | await expect(spyConnection).resolves.toHaveReturnedWith(oneCropType);
| ^
177 | });
178 | });
179 | });

  at Object.toHaveReturnedWith (../node_modules/expect/build/index.js:181:13)
  at Object.<anonymous> (modules/crops/services/crop.service.spec.ts:176:54)
#

the spyConnection method is called inside update function.
why is that and what can i do to fix that?

#

this is the code:

const oneCrop = new Crop();
oneCrop.id = expect.any(Number);
oneCrop.name = "avocado";
oneCrop.color = "green";
oneCrop.typeId = 1;
oneCrop.type = expect.any(CropType);
oneCrop.strains = expect.any(Array<CropStrain>);

const updateCrop = new Crop;
updateCrop.id = expect.any(Number);
updateCrop.name = "banana";
updateCrop.color = "yellow";
updateCrop.typeId = 3;
updateCrop.type = expect.any(CropType);
updateCrop.strains = expect.any(Array<CropStrain>);

const oneCropType = new CropType();
oneCropType.id = 3;
oneCropType.name = "Gidulim";

const mockRepository = {
    save: jest.fn(crop => Promise.resolve(crop)),
    find: jest.fn(),
    findOne: jest.fn()
};
const mockConnectionService = {
    getStrainsByProject: jest.fn(projectId => []),
    getCropTypeById: jest.fn(id => Promise.resolve(oneCropType))
};

describe("update", () => {
        let func;
        let repoSpySave;
        let spyCropId;
        let spyConnection;
        describe("successfull call", () => {
            beforeAll(async () => {
                repoSpySave = jest.spyOn(mockRepository, "save");
                spyCropId = jest.spyOn(cropService, "getCropById");
                spyConnection = jest.spyOn(mockConnectionService, "getCropTypeById");
                mockRepository.findOne.mockImplementationOnce((options: FindOneOptions<Crop>) => oneCrop)
                func = await cropService.update(1, { name: "banana", color: "yellow", typeId: 3 });
            });

           it("should be called 1 time and with CropType id and return a croptype entity",async () => {
                expect(spyConnection).toBeCalledTimes(1);
                expect(spyConnection).toBeCalledWith(updateCrop.typeId);
                await expect(spyConnection).resolves.toHaveReturnedWith(oneCropType);
            });
        });
    });
grizzled sandal
#

Why would the spyConneection return the oneCropType? Also, mockConnectionService.getCropTypeById is already a jest mock. No need to spy on it again, just check expect(mockSonnectionService.getCropTypeById).toHaveReturnedWith(oneCropType)

solemn aspen
#

and it wouldn't call it again in the expect statement? would it check if it have return in the main function call?

grizzled sandal
#

and it wouldn't call it again in the expect statement
No. Jest mocks are pretty much objects with both properties (mock related, like calls, call args, return values) and a functional representation (the actual mock itself) so you can pass the mock without calling it (i.e. without the () at the end) and let jest assert properties on the mock

#

would it check if it have return in the main function call?
That's more difficult. What I do to test this kind of stuff (usually) is keep all assertions related to a single test inside on it/test and use an afterEach that resets the mock after each test, so that it's always a clean slate

solemn aspen
#

i did reset the mocks i just want to check what it was called with how many times and what did it return but im getting:
● Crop Service › update › successfull call › should be called 1 time and with CropType id and return a croptype entity

expect(received).resolves.toHaveReturnedWith()

Matcher error: received value must be a promise

Received has type:  function
Received has value: [Function mockConstructor]

  174 |                 expect(mockConnectionService.getCropTypeById).toBeCalledTimes(1);
  175 |                 expect(mockConnectionService.getCropTypeById).toBeCalledWith(updateCrop.typeId);

176 | expect(mockConnectionService.getCropTypeById).resolves.toHaveReturnedWith(oneCropType);
| ^
177 | });
178 | });
179 | });

  at Object.toHaveReturnedWith (../node_modules/expect/build/index.js:181:13)
  at Object.<anonymous> (modules/crops/services/crop.service.spec.ts:176:72)
#

that of course after i changed to what u suggested

grizzled sandal
#

You don't need to use .resolves when asserting what a mock returned

#

The mock object has an internal returnedValue (or something similar) so it can just be accessed directly, if I recall correctly

solemn aspen
#

but in the main function it's called with await and the mock function returns a promise

grizzled sandal
#

I get that

#

But checking what the .toHaveReturnedWith is slightly different

solemn aspen
#

i tried toEqual but still the returned value result as a [Function mockConstructor]

grizzled sandal
#

What?

#

expect(mockConnectionService.getCropTypeById).toHaveReturnedWith(oneCropType);

solemn aspen
#

● Crop Service › update › successfull call › should be called 1 time and with CropType id and return a croptype entity

expect(received).toEqual(expected) // deep equality

Expected: {"color": "yellow", "id": Any<Number>, "name": "banana", "strains": Any<Array>, "type": Any<CropType>, "typeId": 3}
Received: [Function mockConstructor]

  174 |                 expect(mockConnectionService.getCropTypeById).toHaveBeenCalledTimes(1);
  175 |                 expect(mockConnectionService.getCropTypeById).toHaveBeenCalledWith(updateCrop.typeId);

176 | expect(mockConnectionService.getCropTypeById).toEqual(updateCrop);
| ^
177 | });
178 | });
179 | });

  at Object.<anonymous> (modules/crops/services/crop.service.spec.ts:176:63)
grizzled sandal
#

Why?

solemn aspen
#

● Crop Service › update › successfull call › should be called 1 time and with CropType id and return a croptype entity

expect(jest.fn()).toHaveReturnedWith(expected)

Expected: {"id": 3, "name": "Gidulim"}
Received: {Symbol(async_id_symbol): 78675, Symbol(trigger_async_id_symbol): 78668}

Number of returns: 1

  174 |                 expect(mockConnectionService.getCropTypeById).toHaveBeenCalledTimes(1);
  175 |                 expect(mockConnectionService.getCropTypeById).toHaveBeenCalledWith(updateCrop.typeId);

176 | expect(mockConnectionService.getCropTypeById).toHaveReturnedWith(oneCropType);
| ^
177 | });
178 | });
179 | });

  at Object.<anonymous> (modules/crops/services/crop.service.spec.ts:176:63)
grizzled sandal
#

Hmm, not what I expected there. Okay, give me a few minutes to set up some sort of sample

solemn aspen
#

ok does afterEach executes after all the asynchronous methods executed?

grizzled sandal
#

afterEach is called after every it/test method finishes

grizzled sandal
#

Turns out I was wrong. toHaveReturnedWith will not handle the promise for you and Jest doesn't have a good API around it, so you have to do something hacky like this:

describe('A contrived Example', () => {
  it('should check the return value of an async mock', async () => {
    const serviceMock = {
      getMyMethod: jest.fn().mockResolvedValue('Hello World'),
    }

    const someSuperFunction = async (obj: { getMyMethod: () => Promise<string> }) => {
      return obj.getMyMethod();
    }
    const result = await someSuperFunction(serviceMock);
    expect(result).toBe('Hello World');
    expect(serviceMock.getMyMethod.mock.results[0].value).resolves.toBe('Hello World');
  });
});