У меня есть приложение для реагирования на машинописный текст, переработанное CRA.
Jest импортируется по всему миру.
Чтобы использовать шуточные макеты, мне нужно написать
jest.mock('module-name') ВЫШЕ describe() блока и В ТОМ же файле, например.
import { act, render, screen } from '@testing-library/react';
import { MyComponent } from './MyComponent';
import { something } from 'my-module';
// 150 of my 300 test suites contain these.
// THERE IS NO IMPLEMENTATION and automock is fine
// BUT ONLY IF I opt-in.
// The other 150 test suites might have an implementation;
// Or they might use REAL modules and not mocks.
// This is why I don't want to just turn on `autoMock`.
// And I don't want to add this to `testSetup.js` because the OTHER 150
// do not need them.
// But I want to find a way to call something like `TestUtilities.mockStandard()` for the first 150 test suites.
jest.mock('my-module');
jest.mock('my-module2');
jest.mock('my-module3');
jest.mock('my-module4');
jest.mock('my-module5');
jest.mock('my-module6');
jest.mock('my-module7');
describe('MyComponent', () => {
afterAll(() => {
jest.unmock('my-module');
jest.unmock('my-module2');
jest.unmock('my-module3');
jest.unmock('my-module4');
jest.unmock('my-module5');
jest.unmock('my-module6');
jest.unmock('my-module7');
});
afterEach(() => {
jest.resetAllMocks();
jest.useRealTimers();
});
test('component renders', async () => {
(something as jest.MockedFunction<typeof something>).mockImplementation(() => 'banana');
const { container } = render(<MyComponent></MyComponent>);
expect(screen.queryByText('banana')).toBeVisible();
});
});
Теперь у меня есть несколько сотен наборов тестов и тысячи тестов.
У некоторых из них есть ОБЩИЕ вещи, над которыми хочется посмеяться (например, my-module), но не ВСЕ.
Поэтому я не хочу использовать testSetup.js для издевательств над модулями во ВСЕХ тестах, но Я ХОЧУ найти способ не писать jest.mock() снова и снова.
Итак, я попытался создать модуль утилит следующим образом:
const TestUtilities = {
mockMyModule: () => {
jest.mock('my-module');
},
unmockMyModule: () => {
jest.unmock('my-module');
},
};
export default TestUtilities;
а затем изменил исходный тест:
import { act, render, screen } from '@testing-library/react';
import { MyComponent } from './MyComponent';
import { something } from 'my-module';
import TestUtilities from './TestUtilities';
TestUtilities.mockMyModule();
describe('MyComponent', () => {
afterAll(() => {
TestUtilities.unmockMyModule();
});
afterEach(() => {
jest.resetAllMocks();
jest.useRealTimers();
});
test('component renders', async () => {
(something as jest.MockedFunction<typeof something>).mockImplementation(() => 'banana');
const { container } = render(<MyComponent></MyComponent>);
expect(screen.queryByText('banana')).toBeVisible();
});
});
К сожалению, теперь тест ведет себя так, как будто над my-module не издеваются :(
Есть ли способ повторно использовать эти jest.mock() блоки?
Попробуйте jest.doMock()
@LinDu хорошее предложение. к сожалению, не сработало, код по-прежнему ведет себя так, как будто модуль не подвергался издевательствам.
@morganney Но на самом деле это не будет работать с тестируемым кодом, поскольку он не будет вызывать модуль из моей тестовой утилиты?
@zaitsman Я не понимаю, о чем ты говоришь. Слова такие расплывчатые по сравнению с кодом 🙂. Однако в целом я не вижу реальной выгоды в вызове макета из утилиты по сравнению с вызовом прямо в тестовом примере. Вам все равно придется добавлять код в каждый тестовый пример, который вы хотите имитировать. Можете ли вы предоставить ссылку на свой проект на GitHub? Я мог бы посмотреть, если да.
@morganney Большинство из этих сотен тестов имитируют одни и те же 5 модулей одними и теми же 5 способами. (i18n, маршрутизатор, аутентификация и т. д.) для перехватчиков useXXX по умолчанию. Если бы я мог свернуть даже 500 строк в 100 строк в куче файлов, это заметно сэкономило бы опыт разработки.
Поэтому используйте каталог __mocks__, чтобы предоставить макеты вручную, а затем примите их, вам все равно придется звонить jest.mock туда, где вы хотите, чтобы они были макетированы, но вам не нужно предоставлять реализацию макета снова и снова. Это если один ручной макет работает для всех тестовых случаев. Вы также можете прочитать некоторую глобальную переменную внутри ручного макета, чтобы обеспечить условное макетирование.
@morganney, это на самом деле ничего не сохраняет, потому что эти реализации обычно однострочные или неуказанные (например, простые jest.mock('whatever') как automock). И нет, я не хочу, чтобы ВСЕ модули автоматически подгонялись по умолчанию, я просто хочу, чтобы зависимости, общие для моих корневых/страничных компонентов, не приходилось jest.mock(); повторять снова и снова. __mocks__ полезен только тогда, когда есть возможность повторного использования.
Опять же, вам следует расширить код примера, чтобы предоставить более подробную информацию, потому что, если вам требуются контекстные макеты, зависящие от тестового примера, я не понимаю, как может помочь утилита, просто обертывающая jest.mock. Раньше вы говорили Most of those hundreds of tests mock the same 5 modules the same 5 ways., теперь похоже, говорите обратное. Дважды проверьте пример ручных макетов в документации.
@morganney, пожалуйста, см. редактирование. Честно говоря, у меня есть то, что я считаю тривиальным требованием.
«Все или ничего» — это легко, но ваше требование «что-то так, что-то иначе» делает задачу нетривиальной. Я все еще думаю, что вам следует попробовать использовать ручной макет в каталоге __mocks__ рядом с модулем-n, а затем для 150 тестов, которые требуют специальных реализаций макетирования, используйте глобальный набор var в тестовом примере и прочитайте ручной макет, чтобы изменить имитируемый модуль. . Для остальных 150, которые могут использовать автоматическое имитационное поведение, просто не устанавливайте никаких глобальных переменных в тестах.






Кажется, у вас есть половина тестовых случаев, которые подходят для макетной реализации по умолчанию, а другая половина требует специальных реализаций.
Если это так, вы можете попробовать что-то вроде следующего.
Предположим, что структура каталогов такая:
.
└── src/
└── modules/
├── dependency-n.js
├── __mocks__/
│ └── dependency-n.js
├── dependent.js
└── dependent.test.js
Вы можете использовать глобальные переменные, определенные в тестах, чтобы изменить поведение макетной реализации ручных макетов.
макеты/зависимость-n.js
let mocked = jest.fn();
if (globals.useAltMockForDepN) {
mocked = jest.fn(() => {
// Your custom mocking implementation based on calling test
})
}
export default mocked;
зависимый.test.js
import { something } from 'dependency-n';
jest.mock('dependency-n');
describe('MyDependentComponent', () => {
afterAll(() => {
jest.unmock('dependency-module-n');
});
afterEach(() => {
// Possibly use in afterAll instead
globals.useAltMockForDepN = false
});
test('component renders', async () => {
// Possibly use in beforeAll
globals.useAltMockForDepN = true;
// Now do your testing
});
});
На самом деле это избавляет вас от необходимости создавать собственные реализации макета только в половине ваших тестов, и вам все равно придется вызывать jest.mock() во всех ваших тестах, если только вы не хотите/не можете имитировать каждый импортированный модуль, тогда вы можете установить automock: true. Если вы хотите использовать automock: true, вам не нужно будет вызывать jest.mock() в ваших тестах, но тогда вам придется вызывать jest.unmock() для всех импортированных модулей, которые вы не хотите имитировать.
У меня есть то, что я считаю тривиальным требованием.
Ваш сценарий не является сценарием «все или ничего», поэтому вам нужно написать код, чтобы различать ваши особые случаи, к сожалению, обойти это невозможно.
Вызов jest.mock() и import должны находиться в одной области, и в настоящее время вы не импортируете имитируемый модуль в свои тестовые утилиты.
Вы можете попробовать внести эти изменения.
import myModule from 'my-module'
const TestUtilities = {
mockMyModule: () => {
jest.mock('my-module');
},
unmockMyModule: () => {
jest.unmock('my-module');
},
getMyMockedModule: () => myModule,
};
export default TestUtilities;
На самом деле вы не предоставили воспроизводимый пример, но я хотел поместить свои комментарии в код, чтобы попытаться помочь.
Пожалуйста, ознакомьтесь с моим ответом: stackoverflow.com/a/78469514/2057955 Мне не очень понравилась идея глобальных переменных, так как я не уверен, как она будет работать, когда наборы тестов выполняются параллельно, к тому же это все еще было гораздо больше инструкций, чем просто рассказывать всем разработчикам add this to your spec if you want all our common stuff mocked
Интересное решение. Обычно я не думаю о решениях, связанных с eval, но, поскольку это тестовая среда, работающая на CI-сервере где-то, недоступная для конечных пользователей, маловероятно, чтобы вредоносный код каким-либо образом проник в нее. Что касается глобальных переменных, пока ваши тесты выполняют правильную очистку, все должно быть в порядке, поскольку параллельные тесты выполняются в отдельных процессах. Рад, что вы нашли что-то, что сработало для вас и вашей команды.
Итак, в конце концов, ответ кроется в злых глубинах JavaScript eval.
В частности, нам нужно создать модуль машинописного текста, который будет запускать jest.mock() как eval ВО ВРЕМЯ его загрузки.
Тогда jest runner поймет ложные вызовы в правильном контексте.
Для моего примера OP общий модуль выглядит так:
let mockCall = `(() => {
jest.mock('my-module');
jest.mock('my-module2');
jest.mock('my-module4');
jest.mock('my-module3');
jest.mock('my-module5');
jest.mock('my-module7');
jest.mock('my-module6');
})()`;
eval(mockCall);
export const CommonMocksModule = {
unmock() {
jest.unmock('my-module');
jest.unmock('my-module2');
jest.unmock('my-module3');
jest.unmock('my-module4');
jest.unmock('my-module5');
jest.unmock('my-module6');
jest.unmock('my-module7');
}
}
И спецификация становится:
// So I put this first, but really it should just go before the
// first time any of `my-moduleX` are imported
// otherwise that import will be set and `jest.mock()` won't overwrite it.
// IMPORTANT!! we need to reference `unmock` call
// otherwise import is not run by jest
import { CommonMocksModule } from "./_commonMocks";
import { act, render, screen } from '@testing-library/react';
import { MyComponent } from './MyComponent';
import { something } from 'my-module';
describe('MyComponent', () => {
afterAll(() => {
CommonMocksModule.unmock();
});
afterEach(() => {
jest.resetAllMocks();
jest.useRealTimers();
});
test('component renders', async () => {
(something as jest.MockedFunction<typeof something>).mockImplementation(() => 'banana');
const { container } = render(<MyComponent></MyComponent>);
expect(screen.queryByText('banana')).toBeVisible();
});
});
Достигаем нашей конечной цели — экономить на всех повторениях jest.mock()
jest.mockдолжен находиться в той же области, что иimportимитируемого модуля, поэтому легко попробовать импортировать имитируемый модуль внутри вашей утилиты, а также предоставить средства доступа к импортированному модулю, чтобы его можно было использовать в тестах. Однако вызов методов из вашей утилиты мало чем отличается от использования jest.mock.