Jest mock class instance function - javascript

I have a scenario that seems pretty straight forward but I'm not able to find an example in the doc that does what I need. I've searched SO also but haven't found anything apparently...
I want to mock a function from a class that is initialised and used inside the function I'm actually testing.
Here's an example:
// helpers.js
import API from './api'
export const validateUsername = async (username) => {
const myApi = new API()
try {
await myApi.validate(username)
return 'valid'
} catch (e) {
return 'invalid'
}
}
In my test, I want to mock myApi.validate to make it return a valid response or throw. But for some reason I can't find the way to do it.
// helpers-test.js
it('returns "invalid" if the username is invalid', async () => {
// here I need to mock myApi.validate to return or throw
})
I'm really not sure why I haven't figured this out yet, seems pretty common to do right?
Anyone?

So I figured it out thanks to #Volodymyr.
I think my main issue was to import the lib before mocking it.
jest.mock('path/to/api')
import {Api} from 'path/to/api'
const validateMock = jest.fn().mockImplementation(() => {...})
Api.prototype.validate = validateMock
// now it works

Related

Mocking a function returned by a react hook

I'm building a pagination using the useQuery hook as part of the Apollo Client in React, which exposes a function called fetchMore seen here: https://www.apollographql.com/docs/react/data/pagination/
Everything works fine, but I'm trying to write a test one of the use cases, which is when the fetchMore function fails due to a network error. The code in my component looks like this.
const App = () => {
// Some other component logic
const {loading, data, error, fetchMore} = useQuery(QUERY)
const handleChange = () => {
fetchMore({
variables: {
offset: data.feed.length
},
updateQuery: (prev, { fetchMoreResult }) => {
if (!fetchMoreResult) return prev;
return Object.assign({}, prev, {
feed: [...prev.feed, ...fetchMoreResult.feed]
});
}
}).catch((e) => {
// handle the error
})
}
}
Basically I want to test the case where the fetchMore function function throws an Error. I DON'T want to mock the entire useQuery though, just the fetchMore function. What would be the best way to mock just the fetchMore function in my test?
One way to do it is to just mock the hook
In your spec file:
import { useQuery } from '#apollo/react-hooks'
jest.mock('#apollo/react-hooks',() => ({
__esModule:true
useQuery:jest.fn()
});
console.log(useQuery) // mock function - do whatever you want!
/*
e.g. useQuery.mockImplementation(() => ({
data:...
loading:...
fetchMore:jest.fn(() => throw new Error('bad'))
});
*/
You could also mock the stuff that goes on "behind the scenes" to simulate a network error, do whatever you need to to test your catch.
EDIT:
Search for __esModule: true on this page and you'll understand.
It's probably easier to just mock the whole function and return everything as mock data. But you can unmock it to use the real one so as not to conflict with other tests.

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})))

How to mock a method that accepts no arguments and its supposed to work normally in 1 test, and supposed to throw an error in another test

I'm trying to get 100% coverage on my AWS project but I don't know how to mock methods that don't accept arguments AND are supposed to pass a test that makes them work properly(return values) and another test that makes them throw an error. I can't change the tech I am using so please try to help me with the things I am using right now.
I am using Nodejs, Typescript, Mocha, Chai, nyc and mock-require for mocking.
It's an AWS project so I am working with AWS methods
Here is the function and method, I am mocking describeAutoScalingGroups()
export async function suspendASGroups() {
const autoscaling = new AWS.AutoScaling();
const asgGroups = await autoscaling.describeAutoScalingGroups().promise();
if (!asgGroups.AutoScalingGroups) {
throw new Error("describeAutoScalingGroups inside of suspendAGSGroups didn't return any groups");
}
// some other stuff below
This is the TEST that is supposed to fail(Above this there is a test of the same function which will return regular values)
it('Should throw an error, should fail', async () => {
assertNative.rejects(awsFunctions.resumeAGSGroups());
try {
let result = await awsFunctions.suspendASGroups();
} catch (e) {
assert.isTrue(
e.name == 'Error' &&
e.message == "describeAutoScalingGroups inside of suspendAGSGroups didn't return any groups",
'describeAutoScalingGroups in suspendAGSGroups didnt have the proper error message'
);
}
});
And here is the mock code
public describeAutoScalingGroups() {
const data = (): AWS.AutoScaling.Types.AutoScalingGroupsType => {
return {
// some values here
};
return {
promise: data
};
}
I expect to be able to pass both tests, the one that expects a regular value and one that expects it to throw an error
here is a picture of the coverage: https://i.imgur.com/D6GX0tf.png
I expect that red part to be gone :)
Thank you
In your mock you need to return something for AutoScalingGroupsType that evaluates to false, since you have this check:
if (!asgGroups.AutoScalingGroups) { ... }
So you could simply do this:
public describeAutoScalingGroups() {
return {
promise: () => {
return { AutoScalingGroups: false }
}
};
I was given an answer on reddit so I will post it here too:
You need a different mock for each test. You should setup your mocks in before/beforeEach hooks which you will get from mocha https://mochajs.org/#hooks.
Using sinon would make the mock creation cleaner but if you are stuck with mock-require then it looks like you will need to use https://www.npmjs.com/package/mock-require#mockrerequirepath
--- end of comment
How I did it:
I made another mock file that is the same as the regular mock file, except this one can only fail functions(which is good)
Here is the code in the TEST:
describe('Testing FAILING suspendAGSGroups', () => {
it('Should throw an error, should fail', async () => {
const mock = require('mock-require');
mock.stopAll();
let test = require('./test/mocks/indexFailMocks');
mock.reRequire('./test/mocks/indexFailMocks');
let awsFailing = mock.reRequire('./handler');
// the line above is pretty important, without it It wouldnt have worked, you need to reRequire something even if it's the code of it isn't changed(I only changed the MOCK file but I had to reRequire my main function)
try {
let result = await awsFailing.suspendASGroups();
} catch (e) {
assert.isTrue(
e.name == 'Error' &&
e.message == "describeAutoScalingGroups inside of
suspendAGSGroups didn't return any groups",
'describeAutoScalingGroups in suspendAGSGroups didnt have the proper
error message'
);
}
});
});

Mocking APi calls jest

I have a DataService which is responsible for my API calls - I am trying to mock the api call in jest but it does not seem to be working. I am not sure what I am doing wrong - my DataService seems to be undefined.
Here is the function
const getStepData = (id) => {
return async dispatch => {
try {
dispatch(fetchStepBegin());
const res = await DataService.fetchStepData(id);
const sortedTask = sortedTaskData(res)
const sortedStepData = sortStepData(res)
const newData = createSortedDataForDragAndDrop(sortedTask, sortedStepData)
dispatch(fetchRawStepDataSuccess(res.data))
dispatch(fetchStepDataSuccess(newData))
}
catch (err) {
dispatch(fetchStepError(err))
throw (err)
}
}
}
Here is the test that I have written - I am pretty sure I am mocking incorrectly
it('Data Api end point called with corrent studyId', () => {
jest.mock(DataService);
DataService.fetchStepData() = jest.fn()
CellStepManagementOperations.getStepData(5);
expect(DataService.fetchStepData).toHaveBeenCalledWith(5);
});
I think the problem here is that you are trying to test asynchronous action creators, synchronously. So your expect function doesn't wait for your getStepData to finish before running.
I've had to something very similar to what you're trying to do and I used a library called redux-testkit. Please see the part about testing async action creators with services here. You can even set mock return values for your API services which I've found very helpful when testing.
Using this library, you will be able to await for your getStepData async action creator to complete before running your expect function.
You will have to play around with your code but it might look something like this:
it('Data Api end point called with corrent studyId', () => {
jest.mock(DataService);
DataService.fetchStepData() = jest.fn()
const dispatches = await Thunk(CellStepManagementOperations.getStepData).execute(5);
expect(DataService.fetchStepData).toHaveBeenCalledWith(5);
});

Jest basics: Testing function from component

I have a basic function:
components/FirstComponent:
sayMyName = (fruit) => {
alert("Hello, I'm " + fruit);
return fruit;
}
When I try to test it with Jest inside FirstComponent.test.js:
import FirstComponent from '../components/FirstComponent';
describe('<FirstComponent />', () => {
it('tests the only function', () => {
FirstComponent.sayMyName = jest.fn();
const value = FirstComponent.sayMyName('orange');
expect(value).toBe('orange');
});
});
Test says: Comparing two different types of values. Expected string but received undefined.
Apparently I'm not importing the function to test the right way?
I was not smart enough to understand the Jest documents how to test functions from components..
Is there some simple way to import function from component and test it?
Edit:
This works now using the 'react-test-renderer'
import FirstComponent from '../components/FirstComponent';
import renderer from 'react-test-renderer';
describe('<FirstComponent /> functions', () => {
it('test the only function', () => {
const wrapper = renderer.create(<FirstComponent />);
const inst = wrapper.getInstance();
expect(inst.sayMyName('orange')).toMatchSnapshot();
});
})
You have stubbed the function with one that does not return anything. FirstComponent.sayMyName = jest.fn();
To test the function, typically you can just do
// if static etc
import { sayMyName } from '../foo/bar';
describe('bar', () => {
it('should do what I like', () => {
expect(sayMyName('orange')).toMatchSnapshot();
});
})
This will store the output ("orange") and assert that every time you run this test, it should return orange. If your function stops doing that or returns something else, snapshot will differ and test will fail.
the direct comparison .toBe('orange') will still be possible but the really useful thing about jest is snapshot testing so you don't need to duplicate logic and serialise/deep compare structures or jsx.
if it's a component method, you need to render it first, getInstance() and then call it.

Categories

Resources