- Using with MongoDB · Jest — jest-mongodb(more docs)
1# From 2019
2npm test -- path/to/file.spec.js
1// 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.js
mentioned first in the Getting Started docs, and is great for keeping tests (especially unit) easy to find next to source files
src/__tests__/file.js
lets you have multiple__tests__
directories so tests are still near original files without cluttering the same directories
__tests__/file.js
more 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()
→ returnundefined
if 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.requireActual
1// 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) →
clearAllMocks
1beforeEach(() => {
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});