0
\$\begingroup\$

As some part of my application needs connection to database I wanted to create tests that use mocks to avoid running special instance of database just for testing. That gave me a glimpse of how can I use mocks and I played with them a bit.

I wanted to test express router:

UserRouter.post("/users", async ({ body }: Request, res: Response) => {
    try {
        const userRepository = getCustomRepository(UserRepository);
        const user = userRepository.create({ ...body } as User);
        await userRepository.save(user);
        res.send(user.toJSON());
    } catch (error) {
        res.status(400).send();
    }
});

To do so I created mocks:

jest.mock("typeorm", () => ({
    __esModule: true,
    getCustomRepository: jest.fn(),
    PrimaryGeneratedColumn: jest.fn(),
    Column: jest.fn(),
    CreateDateColumn: jest.fn(),
    UpdateDateColumn: jest.fn(),
    Entity: jest.fn(),
    EntityRepository: jest.fn(),
    Repository: jest.fn(),
}));

const getCustomRepositoryMock = mocked(getCustomRepository);
const MockedRepository = {
    create: jest.fn(),
    save: jest.fn(),
};
const MockedEntity = {
    toJSON: jest.fn(),
};

beforeEach(() => {
    jest.resetAllMocks();
    getCustomRepositoryMock.mockReturnValueOnce(MockedRepository);
});

Then instead of passing and requiring actual data to be return I relayed of testing whether mocks were used, what data were passed to their calls etc. Here is an example:

it("should call save on UserRepository with value returned by call create on UserRepository", async () => {
    MockedRepository.create.mockReturnValue(MockedEntity);
    await request(app).post(path).send();
    expect(MockedRepository.save).toBeCalledWith(MockedEntity);
});

Commit with all my tests is here.

\$\endgroup\$
1
1
\$\begingroup\$

As long as the real implementation that is being mocked here is also unit tested. And the mock is configured to match the expected behavior of the real implementation. You should be fine.

However you gotta remember to change all mock configuration when expectations of the real implementation change. This should not happen often for a library (BC break) but it is much more common in applications.

You are also depending on a specific implementation and if in future the tested object is changed to use different approach/calling other methods of other classes etc, your tests will have to change.

But anyway there is something your test does not test. It does not check that the correct json is returned by the endpoint. And it does not test what happens when exception is thrown. Although the latter should probably be a separate test.

One note for the route, I'm not sure whether save method throwing an exception should be considered a client error (400). It is not client's fault that your db failed.

\$\endgroup\$
1
  • \$\begingroup\$ Good point about changing implementation in the future. I plan to wrap all methods interface that then will be implemented with data source provider and router will be getting it injected, so then such test would be even better. \$\endgroup\$
    – ostojan
    May 13 at 18:32

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.