testingjestjsnestjs

Why save is not called inside jest test


I’m encountering an issue verifying if my mocked save method is being called in my tests. Each time I run my tests, I receive information indicating that the number of calls is 0. However, everything works correctly in the production environment: new data is saved, and the ORM’s save method is called as expected.

Here's my example test

 describe('updateEmail', () => {
    it('should update user email', async () => {
      const user: User = {
        id: 'abc',
        email: 'old@email.org',
        password: 'password',
      };

      const updatedUser: User = Object.assign({}, user);
      updatedUser.email = 'new@email.org';

      const findOneSpy = jest
        .spyOn(repository, 'findOne')
        .mockResolvedValue(user);

      const saveSpy = jest
        .spyOn(repository, 'save')
        .mockResolvedValue(updatedUser);

      expect(
        service.updateEmail({
          oldEmail: user.email,
          newEmail: updatedUser.email,
        }),
      ).resolves.toEqual(updatedUser);

      expect(findOneSpy).toHaveBeenCalledWith({ where: { email: user.email } });

      expect(saveSpy).toHaveBeenCalledWith(updatedUser);
    });
  });

And a simple logic for that

async updateEmail({ oldEmail, newEmail }: UpdateEmailDto) {
    const user = await this.findByEmail(oldEmail);

    if (!user) throw new NotFoundException();

    user.email = newEmail;

    return this.usersRepository.save(user);
  }

There's also an error that I got.

 expect(jest.fn()).toHaveBeenCalledWith(...expected)

    Expected: {"email": "new@email.org", "id": "abc", "password": "password"}

    Number of calls: 0

      133 |       expect(findOneSpy).toHaveBeenCalledWith({ where: { email: user.email } });
      134 |
    > 135 |       expect(saveSpy).toHaveBeenCalledWith(updatedUser);
          |                       ^
      136 |     });
      137 |   });
      138 | });

      at Object.<anonymous> (users/users.service.spec.ts:135:23)
    ```

Solution

  • Just to add a bit of guidance, that could prevent any similar issues in the future : I would advocate for a cleaner test structure.

     describe('updateEmail', () => {
        it('should update user email', async () => {
          // First, a section with your declarations and the mocked values, just like you had, but without the extra spaces
          const user: User = {
            id: 'abc',
            email: 'old@email.org',
            password: 'password',
          };
          const updatedUser: User = { ...user, email: 'new@email.org' };
          const findOneSpy = jest
            .spyOn(repository, 'findOne')
            .mockResolvedValue(user);
          const saveSpy = jest
            .spyOn(repository, 'save')
            .mockResolvedValue(updatedUser);
    
          // Then, a section with your test execution, returning a result
          const result = await service.updateEmail({
            oldEmail: user.email,
            newEmail: updatedUser.email,
          });
    
          // Finally, your assertion section
          expect(result).toEqual(updatedUser);
          expect(findOneSpy).toHaveBeenCalledWith({ where: { email: user.email } });
          expect(saveSpy).toHaveBeenCalledWith(updatedUser);
        });
      });
    

    Since you had the 2nd and 3rd section mixed up together, it probably led to some confusion and a hard time debugging for you. By splitting your test this way, I feel like it would have been easier to see that your service.updateEmail call was not awaited. I hope this helps you in future developments!