#javascript #node.js #unit-testing #jestjs
#javascript #node.js #модульное тестирование #jestjs
Вопрос:
Я пытаюсь имитировать класс, который импортируется в мой код, require
а затем проверяю, вызывается ли метод этого класса.
Я создал пример установки, в которой эта проблема может быть воспроизведена:
// user.js
class User {
getName() {
return "Han Solo"
}
}
module.exports = User
// user-consumer.js
const User = require('./user')
const user = new User()
module.exports.getUserName = () => {
// do things here
return user.getName()
}
// user.test.js
const userConsumer = require('./user-consumer')
const User = require('./user')
jest.mock('./user')
it('should mock', () => {
const user = new User()
jest.spyOn(user, 'getName')
userConsumer.getUserName()
expect(user.getName).toBeCalled()
})
Ошибка, которую я получаю, выглядит следующим образом:
Если бы я использовал синтаксис ES6, это работало бы так, как показано в документации jest: https://jestjs.io/docs/en/es6-class-mocks
Но я, к сожалению, не могу использовать ES6 в этом проекте, так как это потребовало бы большого рефакторинга.
Я также пытался издеваться над классом с помощью параметра module factory
jest.mock('./user', () => {
return jest.fn(() => {
return {
getName: jest.fn(),
}
})
})
Он все еще не работает. Когда я вхожу console.log(user.getName)
в user-consumer.js:5
систему, это показывает, что метод был подделан, но все, что вызывается, user.getName()
не является функцией-потребителем, все равно возвращает «Хан Соло».
Я также пробовал его с и без jest.spyOn
, и он по-прежнему возвращает ту же ошибку.
Это просто невозможно с синтаксисом none ES6?
Комментарии:
1. Вы уже используете синтаксис ES6 везде. Если вы ссылаетесь на модули ES, пожалуйста, будьте более конкретными. Проблема никоим образом не относится к ним конкретно.
2. @EstusFlask извиняется. JS на самом деле не моя сильная сторона; Я неправильно понял документы, связанные в вопросе. Я думал, что синтаксис ES6 здесь уже означает использование
import
. Я говорил, что я пытался использовать синтаксис импорта в этом примере, и это сработало.
Ответ №1:
Проблема в том, что шпионы Jest имеют недокументированное поведение.
Даже если метод прототипа одинаков для всех экземпляров:
new User().getName === new User().getName
Шпион специфичен для экземпляра:
jest.spyOn(new User(), 'getName') !== jest.spyOn(new User(), 'getName')
Если конкретный экземпляр недоступен, это прототип, который необходимо просмотреть:
jest.spyOn(User.prototype, 'getName')
userConsumer.getUserName()
expect(User.prototype.getName).toBeCalled();
Проблема с jest.mock
синтаксисом ES6 не является специфичной. Для того чтобы шпион был доступен для утверждений и изменений реализации, он должен быть где-то открыт. Объявление его вне jest.mock
factory не является хорошим решением, поскольку это часто может привести к состоянию гонки, описанному в руководстве; в этом случае тоже будет. Более безопасным подходом является предоставление ссылки как части макета модуля.
Это было бы проще для модуля ES, потому что таким образом экспорт классов сохраняется отдельно:
import MockedUser, { mockGetName } from './user';
jest.mock('./user', () => {
const mockGetName = jest.fn();
return {
__esModule: true,
mockGetName,
default: jest.fn(() => {
return {
getName: mockGetName
}
})
}
})
...
Для модуля CommonJS с экспортом класса (функции) он будет эффективно отображаться как статический метод класса:
import MockedUser from './user';
jest.mock('./user', () => {
const mockGetName = jest.fn();
return Object.assign(
jest.fn(() => {
return {
getName: mockGetName
}
}),
{ mockGetName }
})
})
...
MockedUser.mockGetName.mockImplementation(...);
userConsumer.getUserName()
expect(MockedUser.mockGetName).toBeCalled();
Комментарии:
1. Хорошо, это имеет большой смысл. Я понимаю, что мне действительно нужно было выставить ссылку на издевательскую функцию, но я поступил неправильно — объявил ее вне
jest.mock
. Который, конечно, не работает, потомуjest.mock
что выполняется до определения переменной.2. Для меня работает как использование прототипа, так и предоставление функции, подобной этой, вы рекомендуете использовать метод прототипа? Мне интересно, влияет ли это на последующие тесты, которые могут вызывать этот метод.
3. Другим обходным путем является использование
var
, которое немного более уродливо и плохо сочетается с__mocks__
любым способом,var mockGetName; jest.mock('./user', () => { mockGetName = jest.fn() ...
. Я перечислил оба способа для полноты картины, потому что proto хорошо работает для шпионских / насмешливых методов прототипов. Возможно, вам понадобится jest.mock для моделирования конструктора, включая методы экземпляра (стрелки) .