Mocking a method on "this" object in jest - javascript

I have the following implementation:
export const actions = {
async submitPhoneNumber(context) {
let data = await this.$axios.
$get('https://jsonplaceholder.typicode.com/todos/1')
// do something with data
return data
}
}
When I run my test I get
TypeError: Cannot read property '$get' of undefined
How do I mock this.$axios.$get?
I looked at mocking global in jest but mocking global is just mocking window.whatever.
I need to mock this object.
This is my test:
import { actions } from '#/store/channel-add'
import flushPromises from 'flush-promises'
describe('channel-add', () => {
it('submits phone number and returns phone code hash', async () => {
let data = await actions.submitPhoneNumber()
await flushPromises()
expect(data).toBeTruthy()
})
})

Here is the solution:
index.ts:
export const actions = {
// I don't know where you get $axios from this, you didn't give the completed code. so I made a fake one for the demo.
$axios: {
$get: url => ''
},
async submitPhoneNumber(context) {
let data = await this.$axios.$get('https://jsonplaceholder.typicode.com/todos/1');
// do something with data
data = this.processData(data);
return data;
},
// for demo
processData(data) {
return data;
}
};
index.spec.ts:
import { actions } from './';
actions.$axios = {
$get: jest.fn()
};
describe('actions', () => {
it('should mock action.$axios.$get method', () => {
expect(jest.isMockFunction(actions.$axios.$get)).toBeTruthy();
});
it('should get data correctly', async () => {
(actions.$axios.$get as jest.Mock<any, any>).mockResolvedValueOnce({ userId: 1 });
const actualValue = await actions.submitPhoneNumber({});
expect(actualValue).toEqual({ userId: 1 });
expect(actions.$axios.$get).toBeCalledWith('https://jsonplaceholder.typicode.com/todos/1');
});
});
Unit tests result:
PASS src/mock-module/axios/index.spec.ts
actions
✓ should mock action.$axios.$get method (4ms)
✓ should get data correctly (4ms)
Test Suites: 1 passed, 1 total
Tests: 2 passed, 2 total
Snapshots: 0 total
Time: 2.181s, estimated 3s

Related

Jest mocking a module with module pattern and having unique functionality on each test

I'm having trouble having a unique implementation of search from this module pattern setup I have (MyConnection.js) - here's similar to what I have:
// MyConnection.js
const MyConnection = () => {
const search = async (q) => {
//...some functionality
};
return {
search,
};
};
//JEST TESTS
const MyConnection = require('../MyConnection')
// this works - but it sets the search implementation
// for the whole test file I believe
jest.mock('../MyConnection', () => {
return jest.fn(()=>{
search: ()=> ['mocked', 'fn', 'results']
})
})
//the jest tests - I want different
// implementations of search in each test
describe('connection tests', ()=>{
it('test one', ()=>{
//Not sure if its something like this to set 'search' for each test? This doesn't work as is
MyConnection.search.mockImplementation((q)=>{`You searched ${q}`})
})
it('test two', ()=>{
MyConnection.search.mockImplementation((q)={q.length>32? 'a': 'b' }})
})
})
How can I get unique Jest mock implementations of that search function for each test?
You should make sure that mock MyConnection always returns the same connection object in your test file and the module you want to test.
MyConnection.js:
const MyConnection = () => {
const search = async (q) => {};
return {
search,
};
};
module.exports = MyConnection;
main.js:
const MyConnection = require('./MyConnection');
async function main(q) {
const conn = MyConnection();
return conn.search(q);
}
module.exports = main;
main.test.js:
const MyConnection = require('./MyConnection');
const main = require('./main');
jest.mock('./MyConnection', () => {
console.log('MyConnection module gets mocked');
const conn = { search: jest.fn() };
return jest.fn(() => conn);
});
const mConn = MyConnection();
describe('connection tests', () => {
it('test one', async () => {
mConn.search.mockImplementation((q) => {
return `You searched ${q}`;
});
const actual = await main('teresa teng');
expect(actual).toBe('You searched teresa teng');
});
it('test two', async () => {
mConn.search.mockImplementation((q) => {
return q.length > 32 ? 'a' : 'b';
});
const actual = await main('_');
expect(actual).toBe('b');
});
});
test result:
PASS examples/70132655/main.test.js (10.454 s)
connection tests
✓ test one (2 ms)
✓ test two (1 ms)
console.log
MyConnection module gets mocked
at examples/70132655/main.test.js:5:11
Test Suites: 1 passed, 1 total
Tests: 2 passed, 2 total
Snapshots: 0 total
Time: 10.903 s
You can't mock it like this:
jest.mock('./MyConnection', () => {
console.log('MyConnection module gets mocked');
return jest.fn(() => { search: jest.fn() });
});
Why? Because every time you call the MyConnection() function in the test file or the file you want to test. It will return a new mock object({ search: jest.fn() }), the mock objects in file under test and test case are not same.

Jest : mock constructor function

I'm having trouble trying to mock a constructor Function.
Here is the main class that I want to test
// main.js
import { Handler } from './handler/handler.js';
var lh = new Handler(windowAlias, documentAlias);
// rest of code
Here is how my Handler function looks. Im trying to mock this
//handler.js
export function Handler(windowAlias, documentAlias) {
this.windowAlias = windowAlias;
this.documentAlias = documentAlias;
this.attachEventListners = function(globalSet) {
// do something
};
}
And the test code:
// main.test.js
import { Handler } from 'handlers/handler'
describe('main script', () => {
it('test handler', () => {
jest.mock('handlers/handler', () => jest.fn())
const mockEventListner = jest.fn()
Handler.mockImplementation(() => ({mockEventListner}))
//call main.js
expect(mockEventListner).toBeCalledTimes(1);
})
I referred this stack overflow and tried but now Im getting error like _handler.Handler is not a constructor on the line that does new Handler(). How can I mock the new Handler call when its a constructor function
You could use jest.mock(moduleName, factory, options) to mock ./handler/handler.js module and Handler class manually.
E.g.
main.js:
import { Handler } from './handler/handler.js';
const windowAlias = 'windowAlias';
const documentAlias = 'documentAlias';
var lh = new Handler(windowAlias, documentAlias);
handler/handler.js:
export function Handler(windowAlias, documentAlias) {
this.windowAlias = windowAlias;
this.documentAlias = documentAlias;
this.attachEventListners = function(globalSet) {
// do something
};
}
main.test.js:
import './main';
import { Handler } from './handler/handler.js';
jest.mock('./handler/handler.js', () => {
return { Handler: jest.fn() };
});
describe('64382021', () => {
it('should pass', async () => {
expect(Handler).toBeCalledWith('windowAlias', 'documentAlias');
});
});
unit test result:
PASS src/stackoverflow/64382021/main.test.js
64382021
✓ should pass (6ms)
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 4.093s, estimated 10s

How to mock jest.spyOn for a specific axios call

How do I mock a specific axios call?
Imagine 2 GET calls:
await axios.get('api/numbers');
await axios.get('api/letters');
Then this is going on in the test:
const mockGet = jest.spyOn(axios, 'get');
mockGet.mockReturnValueOnce(Promise.resolve({ data: 1 }));
mockGet.mockReturnValueOnce(Promise.resolve({ data: 'a' }));
How do I create a condition for mockReturnValueOnce based on the url passed to axios (say for 'api/numbers' -> return Promise.resolve({ data: 1 }))
So far, there is no method of jestjs to do this. Here is the proposal: Parameterised mock return values
Here are the solutions:
Use sinon.js
sinon.js support mock the returned value based on the arguments, there is a method named stub.withArgs(arg1[, arg2, ...]);
Use jest-when - A when(fn).calledWith(args).thenReturn(value) lib for jest
If you insist on using jestjs, since mockReturnValueOnce returns values sequentially(first call, second call, etc.). You can use the below way:
index.js:
import axios from 'axios';
export async function main() {
const numbersRes = await axios.get('api/numbers');
const lettersRes = await axios.get('api/letters');
return { numbersRes, lettersRes };
}
index.test.js:
import { main } from '.';
import axios from 'axios';
describe('59751925', () => {
it('should pass', async () => {
const mockGet = jest.spyOn(axios, 'get');
mockGet.mockImplementation((url) => {
switch (url) {
case 'api/numbers':
return Promise.resolve({ data: 1 });
case 'api/letters':
return Promise.resolve({ data: 'a' });
}
});
const actual = await main();
expect(actual.numbersRes).toEqual({ data: 1 });
expect(actual.lettersRes).toEqual({ data: 'a' });
});
});
Unit test results:
PASS src/stackoverflow/59751925/index.test.js (11.168s)
59751925
✓ should pass (9ms)
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 13.098s

How do change implementation of a Jest mock for a certain test

I've create a file in my test/__mocks__ folder where I mock a npm module. And the link of this file is add to my jest setup. All work great this let me test it pretty nicely. But now for a certain test I need to change the return value from this one. How can I achieve this?
I try to unMock plus setMock etc. But nothing work.
// test/__mocks__/touchId.ts
jest.mock('react-native-touch-id', () => {
return {
isSupported: jest.fn(() => Promise.resolve(true)),
authenticate: jest.fn(() => Promise.resolve(true)),
};
});
And my test
it('should not navigate to main if touch id return false', async () => {
jest.setMock('react-native-touch-id', {
authenticate: jest.fn(() => Promise.resolve(false)),
});
const pinCreation = new PinCreationStore();
const spy = jest.spyOn(NavigationServices, 'navigate');
spy.mockReset();
await pinCreation.verifyUser();
expect(spy).toHaveBeenCalledTimes(0);
spy.mockRestore();
});
Here I still get true so my test crash.
You can use jest.mock() without creating __mocks__ folder.
For example:
react-native-touch-id.ts, I simulate this module in order to keep it simple. You can replace it with real npm module.
const touchId = {
isSupported() {
return false;
},
authenticate() {
return false;
}
};
export default touchId;
react-native-touch-id.spec.ts:
import touchId from './react-native-touch-id';
jest.mock('./react-native-touch-id', () => {
return {
isSupported: jest.fn(() => Promise.resolve(true)),
authenticate: jest.fn(() => Promise.resolve(true))
};
});
describe('react-native-touch-id', () => {
it('t1', () => {
expect(touchId.isSupported()).toBeTruthy();
expect(touchId.authenticate()).toBeTruthy();
});
it('t2', () => {
(touchId.isSupported as jest.MockedFunction<typeof touchId.isSupported>).mockReturnValueOnce(false);
(touchId.authenticate as jest.MockedFunction<typeof touchId.authenticate>).mockReturnValueOnce(false);
expect(touchId.isSupported()).toBeFalsy();
expect(touchId.authenticate()).toBeFalsy();
});
it('t3', () => {
(touchId.isSupported as jest.MockedFunction<typeof touchId.isSupported>).mockReturnValueOnce(true);
(touchId.authenticate as jest.MockedFunction<typeof touchId.authenticate>).mockReturnValueOnce(false);
expect(touchId.isSupported()).toBeTruthy();
expect(touchId.authenticate()).toBeFalsy();
});
});
As you can see, after you mock react-native-touch-id module, you need to import it and mocked again when you want these two methods to have different values. These mocked values will be used in other modules which import and use isSupported and authenticate methods of react-native-touch-id module.
Unit test result:
PASS src/stackoverflow/52172531/react-native-touch-id.spec.ts
react-native-touch-id
✓ t1 (5ms)
✓ t2 (1ms)
✓ t3
Test Suites: 1 passed, 1 total
Tests: 3 passed, 3 total
Snapshots: 0 total
Time: 4.065s

How to restore a mock implementation between tests in jest?

I'm trying to create two tests for distinct functions in the same file.
I have func1 that calls func2 inside it, so I'm spying on func2 and mocking its implementation to test func1, and by the end of the first test I'm restoring the mock.
The problem is that the second test fails because func2 implementation is still mocked.
Why didn't the mock restore?
describe('myClass.func1', () => {
it('returns true', async () => {
const func2spy = jest.spyOn(myClass, 'func2').mockImplementation(() => true);
await expect(func1()).toBe(true);
func2spy.mockRestore();
})
});
describe('myClass.func2', () => {
it('returns false if argument is 0', () => {
expect(func2(0)).toBe(false);
})
});
Here is a demo works for me:
index.ts:
export class MyClass {
constructor() {}
public async func1() {
return this.func2();
}
public func2(int?: number) {
return false;
}
}
index.spec.ts:
import { MyClass } from './';
const myClass = new MyClass();
describe('MyClass.func1', () => {
it('returns true', async () => {
const func2Spy = jest.spyOn(myClass, 'func2').mockImplementation(() => true);
const actualValue = await myClass.func1();
expect(actualValue).toBeTruthy();
func2Spy.mockRestore();
});
});
describe('myClass.func2', () => {
it('returns false if argument is 0', () => {
expect(myClass.func2(0)).toBe(false);
});
});
Unit test result:
PASS src/stackoverflow/50912106/index.spec.ts
MyClass.func1
✓ returns true (7ms)
myClass.func2
✓ returns false if argument is 0 (1ms)
Test Suites: 1 passed, 1 total
Tests: 2 passed, 2 total
Snapshots: 0 total
Time: 4.939s, estimated 5s

Categories