Mocked class method only returns undefined - javascript

I'm testing a React class component which relies on a service for retrieving the user auth.
async populateState() {
const result = await authService.getUserAuthenticationStatus();
const { user, isAuthenticated } = result;
this.setState({
isAuthenticated,
user
});
}
So, I would like to mock the return value for getUserAuthenticationStatus like so:
jest.mock('./components/api-authorization/AuthorizeService');
beforeAll(() => {
jest.spyOn(AuthService, 'getUserAuthenticationStatus').mockReturnValue(
Promise.resolve({
isAuthenticated: true,
user: {}
})
);
});
The problem here, is that when running my tests, the method keeps returning undefined rather than the mock value I had set up in my test. If we take a quick look at the exported member, we can see the class is being instantiated and then exported. Could this be the issue?
const authService = new AuthorizeService();
export default authService;

I think jest.mock is unnecessary here. You can simply spyOn the method since you already have an instance.
beforeAll(() => {
jest.spyOn(authService, 'getUserAuthenticationStatus')
// Returns promise no need to add manually
.mockResolvedValue({ isAuthenticated: true, user: {} });
});
// And make sure you clear the mocks in the end.
afterAll(() => jest.clearAllMocks());
Note: you are spying on the authService, not the AuthService

Related

Vue-test-utils: How do I mock the return of an action in VueX?

I'm writing a test for a Vue component which dispatches to a module store to perform an action and use the result from it.
The action makes a call to our API so I don't want to run the test with that action, but instead mock it and return some dummy data to see that the rest of the method flow works.
So in my test I add a mock store, with a mocked action which just returns hardcoded data, with the intent to see that the component method getData() sets the response of the action to the data.
This doesn't seem to work however and instead it seems as if the real action is called. How do I set it up so that I can avoid calling the real actions and instead use the ones I create for the tests?
Component method, simplified:
methods: {
async getData() {
const response = this.$store.dispatch("global/getDataFromAPI")
if (!response) return
this.$data.data = {...response.data}
}
}
Test code, simplified:
describe('Component.vue', () => {
let localVue;
let vuetify;
let wrapper;
let store;
beforeEach(() => {
localVue = createLocalVue();
localVue.use(Vuex)
vuetify = new Vuetify();
let globalActions = {
getDataFromAPI: async () => {
return {
status: 200,
data: {
information1: "ABC",
information2: 123,
}
}
}
}
store = new Vuex.Store({
modules: {
global: {
actions: globalActions,
namespaced: false
},
}
})
wrapper = mount(Component, {
localVue,
vuetify,
attachTo: div,
mocks: {
$t: () => { },
$store: store,
},
});
});
it('Data is set correctly', async () => {
await wrapper.vm.getData();
const dataInformation1 = wrapper.vm.$data.data.information1;
expect(dataInformation1).toBe("ABC")
});
First, if you want to mock the Vuex Store you don't need to call localVue.use(Vuex). You should call localVue.use(Vuex) only if you are going to use real Vuex Store in test. And if you are going to you must pass store object along with localVue and another arguments, not in mocks property.
Second, to mock your action you can mock store's dispatch method like this:
mocks: {
$store: {
dispatch: () => { dummyData: 'dummyData' }
}
}

Mocking classes in Jest does not call the same method

I'm trying to mock a class that is being imported into my code with require and then testing if a method of that class is getting called.
I've created a sample setup where this issue can be replicated:
// 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()
})
The error I get is as follows:
If I used ES6 syntax this would work as shown on jest's documentation: https://jestjs.io/docs/en/es6-class-mocks
But I unfortunately can't use ES6 on this project as it would require a lot of refactoring.
I also tried mocking the class with the module factory parameter
jest.mock('./user', () => {
return jest.fn(() => {
return {
getName: jest.fn(),
}
})
})
It still doesn't work. When I log console.log(user.getName) in user-consumer.js:5 it does show that the method has been mocked but whatever is called in user.getName() is not the consumer function still returns "Han Solo".
I've also tried it with and without jest.spyOn and it still returns the same error.
Is this just not possible with none ES6 syntax?
The problem is that Jest spies have undocumented behaviour.
Even if prototype method is the same for all instances:
new User().getName === new User().getName
A spy is specific to an instance:
jest.spyOn(new User(), 'getName') !== jest.spyOn(new User(), 'getName')
If a specific instance is unreachable, it's a prototype that needs to be spied:
jest.spyOn(User.prototype, 'getName')
userConsumer.getUserName()
expect(User.prototype.getName).toBeCalled();
A problem with jest.mock isn't specific to ES6 syntax. In order for a spy to be available for assertions and implementation changes, it should be exposed somewhere. Declaring it outside jest.mock factory is not a good solution as it can often result in race condition described in the manual; there will be one in this case too. A more safe approach is to expose a reference as a part of module mock.
It would be more straightforward for ES module because this way class export is kept separately:
import MockedUser, { mockGetName } from './user';
jest.mock('./user', () => {
const mockGetName = jest.fn();
return {
__esModule: true,
mockGetName,
default: jest.fn(() => {
return {
getName: mockGetName
}
})
}
})
...
For CommonJS module with class (function) export, it will be efficiently exposed as class static method:
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();

How to mock implementation of a function called by a service being tested?

I have a NestJS project I'm working on, and I need to write unit tests of my services.
I have a service called BigQueryService that uses #google-cloud/bigquery to access a Big Query dataset and perform a query. I also have another service (lets call it MyService) whose job is to build the query that I need depending on some other logic, and pass it to the BigQueryService, receive the query result from it and return it to the controller, which will in turn send the data through an endpoint.
I need to write unit tests for MyService, and for that I need to mock the BigQueryService in a way that doesn't require it to resolve BigQueryService's dependencies. Here's some of my code:
bigquery.service.ts:
import { Injectable } from '#nestjs/common';
import { BigQuery } from '#google-cloud/bigquery';
...
#Injectable()
export class BigqueryService {
...
constructor(
...
) {
...
}
async executeQuery(querySentence: string): Promise<Array<any>> {
...
return response;
}
}
MyService.service.ts:
import { Injectable } from '#nestjs/common';
import { BigqueryService } from '../bigquery/bigquery.service';
//the following is just a service that helps log the results of this service
import { MyLogger } from '../../config-service/logger.service';
...
#Injectable()
export class MyService {
constructor(
private readonly bigqueryService: BigqueryService,
private readonly logger: MyLogger,
) { }
...
async myFunc(request: RequestInterface): Promise<Array<ResponseInterface>> {
let query = (some code to create a query i want)
return await this.bigqueryService.executeQuery(query);
}
For the tests, I followed the answers in this thread: Mock a method of a service called by the tested one when using Jest
jest.mock('../services/bigquery/bigquery.service', () => jest.fn())
const bq = require('../services/bigquery/bigquery.service')
jest.mock('../config-service/logger.service', () => jest.fn())
const ml = require('../config-service/logger.service')
const executeQuery = jest.fn()
executeQuery.mockReturnValue('desired value')
bq.mockImplementation(() => ({executeQuery}))
describe("Testing consumption moment service function", () => {
it("should call the mock service", () => {
const ms = new MyService(bq,ml)
ms.myFunc(requestBody) //requestBody is a RequestInterface
expect(bq.executeQuery).toHaveBeenCalled
expect(bq.executeQuery).toHaveReturned
});
});
That test passes, so I assume I correctly mocked the bigquery service. But when I try to assert that the value returned is the correct one, I make the test async so that the test wont finish until myFunc is actually done running and I can have a result to compare.
it("should call the mock service", async () => {
const ms = new MyService(bq,ml)
await ms.myFunc(requestBody)
expect(bq.executeQuery).toHaveBeenCalled
expect(bq.executeQuery).toHaveReturned
});
This gets an error: TypeError: this.bigqueryService.executeQuery is not a function
The error points to the line where myFunc calls this.bigqueryService.executeQuery.
I've tried different examples of mocking so that I can mock the call to this function but none got as close as the example above. I also tried to use
jest.spyOn(bq, 'executeQuery')
But that also said that executeQuery wasn't a function: Cannot spy the executeQuery property because it is not a function; undefined given instead
Can someone point me in the right direction here? Is there something I'm missing to make this test work? I thank you all in advance for any help you can give me.
I ended up figuring it out, so if anyone is in the same situation, here is where I found the answer: https://jestjs.io/docs/en/jest-object
The test was fixed like this:
jest.mock('../config-service/logger.service', () => jest.fn())
const ml = require('../config-service/logger.service')
const executeQuery = jest.fn()
describe("Testing service function", () => {
it("should call the mock service", async () => {
jest.mock('../services/bigquery/bigquery.service', () => {
return {
executeQuery: jest.fn(() => 'desired output'),
};
})
const bq = require('../services/bigquery/bigquery.service')
const ms = new MyService(bq,ml)
const p = await ms.myFunc(requestBody) //requestBody is a RequestInterface
expect(bq.executeQuery).toHaveBeenCalled
expect(bq.executeQuery).toHaveReturned
expect(p).toEqual(desired result)
});
});
bq.mockImplementation(() => ({executeQuery}))
Isn't async, try returning a promise
bq.mockImplementation(() => (Promise.resolve({executeQuery})))

mocking a promise in jest mock module factory

I'm writing some units tests and need to mock a named export from a file, but it doesn't seem to work when the export is a function that returns a promise.
For example, I have a module located at lib/api.js that exports a single function called getFoo, which itself returns axios.get('/foo').
In my tests, I want to mock the return value of getFoo to be a resolved promise with the foo payload, so I mock using a module factory:
import * as api from 'lib/api';
jest.mock('lib/api', () => ({
getFoo: jest.fn(() => Promise.resolve('foo')),
});
and then in my test I want to assert that getFoo was called as part of larger process of chained promises.
it('getBar should call getFoo', () => {
return getBar().then(() => {
expect(api.getFoo).toHaveBeenCalled();
});
})
The error I get here is cannot read property 'then' of undefined, as though the jest function used in the mocked module factory is not returning the promise.
If the return value for getFoo is not a promise, it works as expected. If I use jest.fn() as the value for getFoo, and then in the test itself mock the implementation...
it('getBar should call getFoo', () => {
api.getFoo.mockImplementation(() => Promise.resolve("foo"));
return getBar().then(() => {
expect(api.getFoo).toHaveBeenCalled();
});
})
...then it works. My understanding is that the jest.fn(() => {}) should just be a shorter way of defining the implementation, so I am not sure why I am getting the error. Any help appreciated.
Thanks.
More Info:
The lib/api looks like this...
import axios from 'axios';
export const getFoo = (host: string, accessToken: string): Promise<{}> =>
axios.get(`${host}/foo`, {
headers: {
Authorization: `Bearer ${accessToken}`,
'Content-Type': 'application/json',
}
})
// return foo
.then((response) => response.data)
.catch(() => {
throw new Error('Failed to load foo');
});
When I console log the mocked getFoo function while the test is running, it shows up as...
getFoo function mockConstructor() {
return fn.apply(this, arguments); }
First thing, you need to mock it before importing it:
jest.mock('lib/api');
import * as api from 'lib/api';

Jest mocking a dependency that's instantiated before the module export

I'm trying to test a file which exports one default function, and also instantiates an object before the exported function that needs to remain as the same instance every time the exported function is called.
Here's a simplified version of the module:
import { HttpClient } from '../client'
const client = new HttpClient()
export default (params) => {
const url = 'www.'
// There's a little bit more code here to generate the url based on the params passed in
return client.get(url)
}
I want to test that the url send to the client.get() function is correct, so here's the test:
import myModule from '../myModule'
import { HttpClient } from '../client'
jest.mock('../client')
const mockHttpClient = { get: jest.fn(() => 'test') }
HttpClient.mockImplementation(() => mockHttpClient)
it('should parse the query param string', () => {
console.log(myModule({}))
})
When the test runs, the response from myModule({}) is always undefined.
However, if I move the const client = new HttpClient() line down to just inside the exported function, it works correctly and myModule({}) returns test.
I need the HttpClient to only be defined once, and have the same instance used every time the function is called, so it has to be instantiated outside the function. How can I mock that object creation to return my custom value? Thanks.
This because when you call import {} from '../client' it will immediately invoke new HttpClient(), before you mock the constructor. you could change your mock to
jest.mock('../client', () => ({
HttpClient: jest.fn(() => ({
get: jest.fn(() => 'test'),
}));
}));
but usually anything involve network IO is async, so you might actually need to return a promise like jest.fn(() => Promise.resolve('test')) or jest.fn().mockResolvedValue('test')

Categories

Resources