Jest: Как правильно протестировать сервис Javascript с использованием MongoDB - PullRequest
0 голосов
/ 12 февраля 2020

Я абсолютный новичок в Jest.

У меня UserService с использованием Dependency Injection.

public async getAll() {
  const userRecords = await this.userModel.find().select('name').catch((e) => {
    throw new HttpException(500, 'Error while fetching users.', e)
  });
  return <[IUser]>userRecords;
}

Я бы хотел проверить эту функцию. Вот тесты, которые я мог бы выполнить:

  1. Вызов маршрута и проверка правильности полученного JSON
  2. Получение содержимого БД и проверка его ожидаемого
  3. Просто протестируйте функцию getAll

Я думаю, 1 и 2 очевидны и охватывают разные вещи. 1 охватывает часть запроса, 2 - часть БД. Но как насчет № 3? Как «просто протестировать» функцию getAll?

Я пробовал это:

const userModel = {
  find: (user) => {
    return [
      { id: 'user1' },
      { id: 'user2' }
    ]
  },
};
const userService = new UserService(userModel);
const userRecords = await userService.getAll();

expect(argumentRecord).toBeDefined();

Но, очевидно, что это сбой, потому что select is undefined.

Должен ли я также издеваться select()? Должен ли я организовать свой код по-другому?

Ответы [ 2 ]

1 голос
/ 12 февраля 2020

Да, вы должны издеваться select. И не только это, но и все, что используется внутри функции, и проверьте, правильно ли они выполнены. Я бы сделал это:

class SomeClass {
    public async getAll() {
      const userRecords = await this.userModel.find().select('name').catch(this.errorHandler);

      return <[IUser]>userRecords;
    }

    public errorHandler(e) {
        throw new HttpException(500, 'Error while fetching users.', e);
    }
}

// this is just an example, it should be the same type as your expected returned output
const whatever = Math.random();

const fakeCatch = jest.fn(() => whatever);

const fakeSelect = jest.fn(() => {
    return {
        catch: fakeCatch
    }
});
const fakeFind = jest.fn(() => {
    return {
        select: fakeSelect
    };
});
const fakeUserModel = {
    find: fakeFind,
}

const userService = new UserService(fakeUserModel);
const userRecords = await userService.getAll();

// should return the correct result
expect(userRecords).toEqual(whatever);

// should execute find
expect(fakeFind).toHaveBeenCalledTimes(1);

// should execute select with 'name' parameter
expect(fakeSelect).toHaveBeenCalledTimes(1);
expect(fakeSelect).toHaveBeenCalledWith('name');

// should execute catch with this.errorHandler
expect(fakeCatch).toHaveBeenCalledWith(userService.errorHandler);
1 голос
/ 12 февраля 2020

Если бы написать этот тест, я бы издевался над функциями, используя jest.fn(implementation), чтобы можно было обеспечить ожидания при вызовах функций.

const userQuery = {
    select: jest.fn(() => Promise.resolve([]))
};

const userModel = {
    find: jest.fn(() => userQuery)
};

const userService = new UserService(userModel);
const userRecords = await userService.getAll();

expect(userRecords).toEqual([]);
expect(userModel.find).toHaveBeenCalled();
expect(userQuery.select).toHaveBeenCalledWith('name');

Выполнение ожиданий при вызовах функций может показаться излишним, но это явно подтверждает, что макет действительно используется getAll.

. Я бы также структурировал тесты таким образом, чтобы я мог тестировать различные пути кода без повторной реализации весь макет.

describe('getAll()', () => {

    let userQuery, userModel, userService;
    beforeEach(() => {
        userQuery = {
            select: jest.fn(() => Promise.resolve([]))
        };

        userModel = {
            find: jest.fn(() => userQuery)
        };

        userService = new UserService(userModel);
    });

    afterEach(() => {
        expect(userModel.find).toHaveBeenCalled();
        expect(userQuery.select).toHaveBeenCalledWith('name');
    });

    it('should get the user names', async () => {
        const users = [{
            name: 'john'
        }, {
            name: 'jane'
        }];
        userQuery.select.mockImplementation(() => Promise.resolve(users));

        await expect(userService.getAll()).resolves.toBe(users);
    });

    it('should handle errors', async () => {
        const error = new Error('Fake model error');
        userQuery.select.mockImplementation(() => Promise.reject(error));

        await expect(userService.getAll()).rejects.toMatch({
            status: 500,
            message: 'Error while fetching users.',
            cause: error
        });
    });
});

Этот код не проверен, поэтому он может работать некорректно, но, надеюсь, он в достаточной степени излагает идею.


Хотя это не имеет прямого отношения к вашему вопросу Я бы не стал смешивать async/await с традиционной обработкой обещаний.

public async getAll() {
    try {
        return <[IUser]> await this.userModel.find().select('name');
    } catch (e) {
        throw new HttpException(500, 'Error while fetching users.', e)
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...