- Using with MongoDB · Jest — jest-mongodb(more docs)
1# From 2019
2npm test -- path/to/file.spec.js1// We wanna mock this class
2export class ProductsClient {
3 async getById(id) {
4 const url = `http://localhost:3000/api/products/{id}`;
5 const response = await fetch(url);
6 return await response.json();
7 }
8}
9
10// We wanna test this class
11export class ProductManager {
12 async getProductToManage(id) {
13 const productsClient = new ProductsClient();
14 const productToManage = await productsClient.getById(id)
15 .catch(err => alert(err));
16 return productToManage;
17 }
18}1// In the file of testing class ProductManager
2import { ProductsClient } from './ProductsClient';
3jest.mock('./ProductsClient');
4
5// A "mock" getById() which returns "undefined" will be created
6// But we want the mock function returns a value as we want
7
8// assign a mock function to the ProductsClient's 'getById' method
9const mockGetById = jest.fn();
10ProductsClient.prototype.getById = mockGetById;
11// We can make the mock function return what we want
12mockGetById.mockReturnValue(Promise.resolve(expectedProduct));
13
14// Restore the state of the original class
15mockFn.mockClear() // or something like that (check the doc) (SO source) The conventions for Jest, in order of best to worst in my opinion:
src/file.test.jsmentioned first in the Getting Started docs, and is great for keeping tests (especially unit) easy to find next to source files
src/__tests__/file.jslets you have multiple__tests__directories so tests are still near original files without cluttering the same directories
__tests__/file.jsmore like older test frameworks that put all the tests in a separate directory; while Jest does support it, it's not as easy to keep tests organized and discoverable
Becareful that,
For example, "2011-10-10" (date-only form), "2011-10-10T14:48:00" (date-time form), or "2011-10-10T14:48:00.000+09:00" (date-time form with milliseconds and time zone) can be passed and will be parsed. When the time zone offset is absent, date-only forms are interpreted as a UTC time and date-time forms are interpreted as local time.
→ Sử dụng UTC time sẽ an toàn hơn (và ko bị ảnh hưởng bởi local time hay timezone)
👇 This SO.
If the tests do make changes to those conditions, then you would need to use
beforeEach, which will run before every test, so it can reset the conditions for the next one.👉 Read more.
- They are all used for mocking a method.
jest.fn()→ returnundefinedif no implementation is provided. (use these methods to implement)
jest.spyOn()→ default it calls the original implementation, it stores the original implementation in memory. ← restore bymockRestore()
Put
import 'dotenv/config'; at the beginning of the .spec file. It will use the current env file.If we wanna mock some variables?
1describe('process.env', () => {
2 const env = process.env
3
4 beforeEach(() => {
5 jest.resetModules() // important!!!
6 process.env = { ...env }
7 })
8
9 afterEach(() => {
10 process.env = env
11 })
12})
13
14it('should mock process.env', () => {
15 process.env.NODE_ENV = 'development'
16 console.log(process.env.NODE_ENV) // Will be "development"
17})
18
19it('should not mock process.env', () => {
20 console.log(process.env.NODE_ENV) // Will be "test"
21})1// Use in a single suit
2describe('Enabled Zerobounce validation', () => {
3 beforeAll(() => {
4 process.env.ZEROBOUNCE_USE = 'true';
5 });--runInBand(or-i): source — Run all tests serially in the current process, rather than creating a worker pool of child processes that run tests.
- If using
spyOn()→restoreAllMocks()inafterEach()
⭐ Good to read: Mocking functions and modules with Jest | pawelgrzybek.com
👇 Source.
Mocking a named import → official
jest.mock()1// Usage
2import { getTime } from './time';
3
4// test.js
5jest.mock('./time', () => ({
6 getTime: () => '1:11PM',
7}));Mocking only the named import (and leaving other imports unmocked) → official
jest.requireActual1// Usage
2import { getTime, isMorning } from './time';
3
4// test.js
5jest.mock('./time', () => ({
6 ...jest.requireActual('./time'),
7 getTime: () => '1:11PM',
8 // isMorning will return its true value
9}));Mocking a default import
1// Usage
2import getDayOfWeek from './time';
3
4// test.js
5jest.mock('./time', () => () => 'Monday');Mocking default and named imports
1// Usage
2import getDayOfWeek, { getTime } from './time';
3
4// test.js
5jest.mock('./time', () => ({
6 __esModule: true,
7 default: () => 'Thursday'
8 getTime: () => '1:11PM',
9}));Changing what the mock returns per test
→ Be careful: Calling
mockReturnValue inside a test still changes the mock for all other tests after it. ← use mockReturnValueOnce instead!1import getDayOfWeek from './time';
2
3jest.mock('./time', () => jest.fn());
4
5test('App renders Monday', () => {
6 getDayOfWeek.mockReturnValue('Monday');
7 //...
8});
9
10test('App renders Tuesday', () => {
11 getDayOfWeek.mockReturnValue('Tuesday');
12 //...
13});
14
15test('App renders Monday, again', () => {
16 // Fails
17});Clearing mocks between tests (make sure it’ll be called once) →
clearAllMocks1beforeEach(() => {
2 jest.clearAllMocks();
3});Mocking multiple modules with chaining,
1jest.mock('./time', () => jest.fn())
2 .mock('./space', () => jest.fn());→ Use
mockName() and getMockName()This is just an example of my very specific case. It may not good (or accurate) enough!
1// df.service.ts
2export class DialogflowService {
3 constructor(private loggerService, botId){}
4 method() {
5 this.loggerService.log('abc');
6 // main codes
7 }
8}1// df.service.spec.ts
2beforeAll(() => {
3 loggerService = {
4 log: jest.fn()
5 };
6 const botId = 'fake-bot-id';
7 service = new DialogflowService(loggerService, botId);
8})1// my-class.ts
2class MyClass {
3 constructor(name) {
4 this.name = name;
5 }
6 methodOne() {
7 return 1;
8 }
9 methodTwo() {
10 return 2;
11 }
12}
13export default MyClass;1// my-class.spec.ts
2import testSubject from './testSubject';
3jest.mock('./myClass', () => ({
4 name: 'Jody',
5 methodOne: () => 10,
6 methodTwo: () => 25,
7}));If a class is not a default export from a module (official doc),
1import {SoundPlayer} from './sound-player';
2jest.mock('./sound-player', () => {
3 // Works and lets you check for constructor calls:
4 return {
5 SoundPlayer: jest.fn().mockImplementation(() => {
6 return {playSoundFile: () => {}};
7 }),
8 };
9});Read this official doc.
1// Original class
2export default class Person {
3 constructor(first, last) {
4 this.first = first;
5 this.last = last;
6 }
7 sayMyName() { // wanna mock
8 console.log(this.first + " " + this.last);
9 }
10 bla() { // wanna keep
11 return "bla";
12 }
13}1// Test
2import Person from "./Person";
3
4test('Modify only instance', () => {
5 let person = new Person('Lorem', 'Ipsum');
6 let spy = jest.spyOn(person, 'sayMyName').mockImplementation(() => 'Hello');
7
8 expect(person.sayMyName()).toBe("Hello");
9 expect(person.bla()).toBe("bla");
10
11 // unnecessary in this case, putting it here just to illustrate how to "unmock" a method
12 spy.mockRestore();
13});👇 ⭐ Source (read the exmplanation there)
If we wanna import a constant?
1// app.js
2import { CAPITALIZE } from './config';
3export const sayHello = (name) => {
4 let result = 'Hi, ';
5
6 if (CAPITALIZE) {
7 result += name[0].toUpperCase() + name.substring(1, name.length);
8 } else {
9 result += name;
10 }
11 return result;
12};1// app.spec.ts
2import { sayHello } from './say-hello';
3import * as config from './config';
4// 👇 For typescipt: type casting
5const mockConfig = config as { CAPITALIZE: boolean };
6
7jest.mock('./config', () => ({
8 __esModule: true, // important for using import * from ...
9 CAPITALIZE: null // default: null if export default CAPITALIZE;
10}));
11
12describe('say-hello', () => {
13 test('Capitalizes name if config requires that', () => {
14 mockConfig.CAPITALIZE = true;
15
16 expect(sayHello('john')).toBe('Hi, John');
17 });
18
19 test('does not capitalize name if config does not require that', () => {
20 mockConfig.CAPITALIZE = false;
21
22 expect(sayHello('john')).toBe('Hi, john');
23 });
24});How about a method?
(We don’t need
import * from ... and also no need __esModule)1import { sayHello } from './say-hello';
2import { shouldCapitalize } from './config';
3
4jest.mock('./config', () => ({
5 shouldCapitalize: jest.fn()
6}));
7
8describe('say-hello', () => {
9 test('Capitalizes name if config requires that', () => {
10 shouldCapitalize.mockReturnValue(true);
11
12 expect(sayHello('john')).toBe('Hi, John');
13 });
14
15 test('does not capitalize name if config does not require that', () => {
16 shouldCapitalize.mockReturnValue(false);
17
18 expect(sayHello('john')).toBe('Hi, john');
19 });
20});(Optional) Mock a client of @google-cloud/dialogflow
got has itself got and other methods get, post,…Read this official doc.
There is also a case where in the main funtion, we
throw new ClassName() and we want to catch this error in the test file!1// not working
2expect(new TestObject()).toThrow();
3// Because new TestObject() is evaluated first
4
5// working
6expect(() => { new TestObject() }).toThrow();Bên trong hàm cần được test có 1 hàm (
updateEntityTypeSpy) và ta muốn mock hàm này throw an error để có thể test.1it('If there are problems with the API?', async () => {
2 // Mock
3 const serviceIdObjects = fakeServiceIdObjects().entities;
4 const lexIds = serviceIdObjects.slice(0, 2).map(obj => obj.idetaId);
5 updateEntityTypeSpy = jest
6 .spyOn(service, 'updateEntityType')
7 .mockRejectedValueOnce('Error')
8 .mockResolvedValueOnce();
9
10 // Call the method
11 await service
12 .updateLexiconsInDialogflow(...)
13 .catch(() => {
14 expect(updateEntityTypeSpy).toBeCalledTimes(1);
15 });
16
17 // Expectations
18 expect(updateIntentSpy).not.toBeCalled();
19 expect(removeLexiconServiceIdSpy).not.toBeCalled();
20 expect(removeLexiconStatusSpy).not.toBeCalled();
21});1it('should return an object of given type', done => {
2 of(mockSnapshot()).pipe(service.methodToTest()).subscribe(res => {
3 expect(res).toStrictEqual(fakeReturnedObj);
4 done();
5 });
6});Create a “util” function for all tests → create it in
.stub.ts and then export it and import it in the .spec.ts file.❇️
mach-o file, but is an incompatible architecture (have 'x86_64', need 'arm64'))(On Mac M1) → make sure the terminal is open under
arm architecture (run arch too see) → then reinstall everything,1rm -rf node_modules && rm package-lock.json && npm i❇️
Jest did not exit one second after the test run has completed.(Not a perfect solution) ← just not to see but not solve the problem internally
1# Run with
2--detectOpenHandles --forceExit✳️ Unexpected directive 'TinyBotSpinnerComponent' imported by the module 'DynamicTestModule'. Please add an @NgModule annotation.
Still not know!!!!
✳️ RequestError: getaddrinfo ENOTFOUND us-undefined
→ Forget to
.mockResolvedValue() 1// Error
2const getAgentSpy = jest.spyOn(service, 'getAgent');
3// Without error
4const getAgentSpy = jest.spyOn(service, 'getAgent').mockResolvedValue(null);It’s good to do something like this,
1describe('💎 freshExport()', () => {
2 let getAgentSpy: jest.SpyInstance;
3
4 beforeEach(() => {
5 jest.clearAllMocks();
6 getAgentSpy = jest.spyOn(service, 'getAgent');
7 });
8
9 it('should call all the necessary methods', async () => {
10 getAgentSpy.mockResolvedValue(null);
11 });
12});