New to node.js. I am writing a JS API client that wraps the underlying axios library. In the unit tests I am mocking axios using Jest.
In the constructor of my API class I pass in a URL, and use the axios.create function to create a custom instance of axios and bind it to the the client property.
The problem arises when I mock the axios dependency with jest.mock('axios') - A TypeError is being thrown in the test when an attempt is made to call axios.get:
TypeError: Cannot read property `get` of undefined
I understand why this is happening, but I've not found a way to enable me to mock axios and not have the client field be undefined. Is there a way to get around this, other than injecting axios through the constructor?
Client code and test below:
client.js
jest.mock("axios");
const axios = require("axios");
const mockdata = require("./mockdata");
const ApiClient = require("../../../src/clients/apiclient");
const BASE_URL = "https://www.mock.url.com"
const mockAxiosGetWith = mockResponse => {
axios.get.mockResolvedValue(mockResponse);
};
test("should make one get request", async () => {
mockAxiosGetWith(MOCK_RESPONSE)
// the client field in apiclient is undefined
// due to the jest module mocking of axios
const apiclient = new ApiClient.AsyncClient(BASE_URL);
// TypeError: Cannot read property `get` of undefined
return await apiclient.get("something").then(response => {
expect(axios.get).toHaveBeenCalledTimes(1);
});
});
client.test.js
const axios = require("axios");
const getClient = (baseUrl = null) => {
const options = {
baseURL: baseUrl
};
const client = axios.create(options);
return client;
};
module.exports = {
AsyncClient: class ApiClient {
constructor(baseUrl = null) {
this.client = getClient(baseUrl);
}
get(url, conf = {}) {
return this.client
.get(url, conf)
.then(response => Promise.resolve(response))
.catch(error => Promise.reject(error));
}
}
};
You need to mock axios so it will return an object which holds the create function which should return the object with the get
import axios from 'axios'
jest.mock('axios', () => ({create: jest.fn()}))
test("should make one get request", async () => {
const get = jest.fn(()=>Promise.resolve(MOCK_RESPONSE))
axios.create.mockImplementation(()=>({get}))
const apiclient = new ApiClient.AsyncClient(BASE_URL);
await apiclient.get("something")
expect(get).toHaveBeenCalledTimes(1);
});
Related
I want to test my AWS Lambda functions written in typescript. The problem is that I'm mocking a service, which initially works fine and has all expected functions, but from one line to the other it turns to undefined, even though in the console.log right before it is defined.
My lambda function:
export const changePassword = LambdaUtils.wrapApiHandler(
async (event: LambdaUtils.APIGatewayProxyEvent, context: Context) => {
const accessToken = event?.body?.accessToken;
const previousPassword = event?.body?.previousPassword;
const proposedPassword = event?.body?.proposedPassword;
// let authenticationService = Injector.getByName("AuthenticationService"); // I tried mocking this object but given the dependency injection it is always undefined
//Request
const authenticationService = new AuthenticationService(); // Here it is perfectly mocked
console.log("-> authenticationService", authenticationService); // Here it is also defined with all functions
const response = await authenticationService.changePassword(
accessToken,
previousPassword,
proposedPassword
); //Here authenticationService is undefined
console.log("response in auth_handler", response);
if (response?.statusCode === 200) {
return new LambdaUtilities().createHttp200Response(response);
}
}
);
My test component:
jest.mock("../../../controllers/auth.service");
const MockedAuthService = AuthenticationService as jest.Mock<AuthenticationService>;
.....
test(`Test successful response of changePassword Repo function"`, async () => {
const accessToken = "accessToken";
const previousPassword = "123456";
const proposedPassword = "654321";
const mockAuthService = new MockedAuthService() as jest.Mocked<AuthenticationService>;
console.log("-> mockAuthService", mockAuthService);
const event = {
body: {
accessToken,
proposedPassword,
previousPassword
}
};
const response = await changePassword(event, {}, {});
console.log("response", response);
expect(response).not.toBeNull();
});
Here are screenshots from a debug with PyCharm:
I am currently realized I shouldn't be calling api straight through network request while using jestjs to check for api.
I have been looking at some posts + youtube tutorials such as https://www.leighhalliday.com/mocking-axios-in-jest-testing-async-functions Mock inner axios.create() but still a bit confused and now sure how to get this to work.
I created a registration api and wanted to do test on it, and after reading the mockup documentation and so on. I have something like...this as my folder structure
this is how my base_axios/index.js looks like, BASE_URL is just something like http://localhost:3000
const axios = require('axios');
const { BASE_URL } = require('../base');
const baseOption = {
// without adding this, will not be able to get axios response status
validateStatus: function (status) {
return status >= 200 && status <= 503;
},
baseURL: BASE_URL,
headers: { 'Content-Type': 'application/json' },
};
module.exports = axios.create(baseOption);
apis/auth.js
const request = require('./base_axios');
module.exports = {
register: data => request.post('/auth/register', data),
};
mocks/axios.js
const mockAxios = jest.genMockFromModule('axios');
mockAxios.create = jest.fn(() => mockAxios);
module.exports = mockAxios;
routes/auth/register.js
const Auth = require('../../apis/auth');
const mockAxios = require('axios');
test('calls axios for registration', async () => {
// this should give me an error and show which api has been called
expect(mockAxios.post).toHaveBeenCalledWith('what is the api');
const response = await Auth.register();
console.log(response, 'response'); // response here gives me undefined
});
I am not getting which api call is being called and te response gives me undefined
also getting this error from jest expect(jest.fn()).toHaveBeenCalledWith(...expected)
Thanks in advance for anyone with advice and suggestions.
PS
jest.config.js
module.exports = {
clearMocks: true,
coverageDirectory: "coverage",
// The test environment that will be used for testing
testEnvironment: "node",
};
You can mock axios and an implemtation for it as below:
jest.spyOn(axios, 'post').mockImplementation();
For an example:
test('calls axios for registration', async () => {
const mockDataRequest = {};
const mockPostSpy = jest
.spyOn(axios, 'post')
.mockImplementation(() => {
return new Promise((resolve) => {
return resolve({
data: {},
});
});
});
expect(mockPostSpy).toHaveBeenCalledTimes(1);
expect(mockPostSpy).toBeCalledWith(
`/auth/register`,
expect.objectContaining(mockDataRequest)
);
});
I have a node module which exports a few classes, one of which is Client, which I use to create a client (having a few APIs as methods).
I'm trying to test my module which uses this node module as a dependency using Jest. However, I've been unable to successfully mock the one method (say search()) in the Client class.
Here is my spec for myModule:
//index.spec.ts
import * as nock from 'nock';
import * as externalModule from 'node-module-name';
import { createClient } from './../../src/myModule';
describe(() => {
beforeAll(() => {
nock.disableNetConnect();
});
it('test search method in my module', () => {
jest.mock('node-module-name');
const mockedClient = <jest.Mock<externalModule.Client>>externalModule.Client;
const myClient = createClient({/*params*/}); //returns instance of Client class present in node module by executing Client() constructor
myClient.searchByName('abc'); //calls search API - I need to track calls to this API
expect(mockedClient).toHaveBeenCalled();
expect(mockedClient.prototype.search).toHaveBeenCalledWith('abc');
});
});
This, however, doesn't create a mock at all and triggers a nock error since the search API tries to connect to the url (given through params).
I've also tried mocking the Client class like the following. While successfully creating a mock for the Client class and also the search API (verified that search() is also mocked through console logs), it gives me an error while I try to check if search() has been called.
externalModule.Client = jest.fn(() => { return { search: jest.fn(() => Promise.resolve('some response')) } });
//creates the mock successfully, but not sure how to track calls to 'search' property
const client = myModule.createClient(/*params*/);
client.searchByName('abc');
expect(externalModule.Client).toHaveBeenCalled(); //Successful
expect(externalModule.Client.prototype.search).toHaveBeenCalled(); //returns error saying "jest.fn() value must be a mock function or spy, Received: undefined"
I'm not sure what I'm doing wrong. Thank you in advance.
Mocking whole module
Try moving jest.mock to the top of file
//index.spec.ts
const search = jest.fn();
jest.mock('node-module-name', () => ({
Client: jest.fn(() => ({ search }))
}));
import * as nock from 'nock';
import * as externalModule from 'node-module-name';
import { createClient } from './../../src/myModule';
describe(() => {
beforeAll(() => {
nock.disableNetConnect();
});
it('test search method in my module', () => {
const myClient = createClient({/*params*/});
myClient.searchByName('abc');
expect(externalModule.Client).toHaveBeenCalled();
expect(search).toHaveBeenCalledWith('abc');
externalModule.Client.mockClear();
search.mockClear();
});
});
Mocking only Client
Create search constant and track it.
const search = jest.fn();
externalModule.Client = jest.fn(() => ({ search }));
const client = myModule.createClient(/*params*/);
client.searchByName('abc');
expect(externalModule.Client).toHaveBeenCalled();
expect(search).toHaveBeenCalled();
Here is how I mocked it. I had to change naming and removing some code to avoid exposing original source.
jest.mock('../foo-client', () => {
return { FooClient: () => ({ post: mockPost }) }
})
Full code.
// foo-client.ts
export class FooClient {
constructor(private config: any)
post() {}
}
// foo-service.ts
import { FooClient } from './foo-client'
export class FooLabelService {
private client: FooClient
constructor() {
this.client = new FooClient()
}
createPost() {
return this.client.post()
}
}
// foo-service-test.ts
import { FooService } from '../foo-service'
const mockPost = jest.fn()
jest.mock('../foo-client', () => {
return { FooClient: () => ({ post: mockPost }) }
})
describe('FooService', () => {
let fooService: FooService
beforeEach(() => {
jest.resetAllMocks()
fooService = new FooService()
})
it('something should happened', () => {
mockPost.mockResolvedValue()
fooService.createPost()
})
})
Property 'getUsersTotalPayout` does not exist on type typeof PayoutApi
My Class:
import { bind } from 'decko';
import BaseApi from './Base';
import * as NS from './types';
class PayoutApi extends BaseApi {
#bind
public async getUsersTotalPayout(userId: string): Promise<number> {
const params: NS.IGetUsersTotalPayoutRequest = { userId };
const response = await this.actions.get<{ payout: number }>(
'/api/get-total-payout',
params,
);
return response.data.payout;
}
}
export default PayoutApi;
The test file:
import PayoutApi from './LiquidityPool';
const endpoint = '/api/get-total-payout';
const userId = 'foo';
jest.mock(endpoint, () => ({
getUsersTotalPayout: jest.fn(() => Promise.resolve({ data: { payout: 100.21 } }))
}));
describe('API: getUsersTotalPayout', () => {
it('should make a request when we get images', () => {
// const testApi = new PayoutApi();
expect(PayoutApi.getUsersTotalPayout(userId)).toHaveBeenCalledWith(endpoint, 'GET');
});
});
Getting that error on expect(PayoutApi.getUsersTotalPayout).toHaveBeenCalledWith(endpoint, 'GET');
You are currently trying to call method on the class. Since it is not static you should instantiate object of class first.
let api = new PayoutApi();
expect(api.getUsersTotalPayout(userId).....)
since jest.mock mocks module not endpoint or XHR request your test will try sending live request to /api/get-total-payout. For handling that it's needed to know what XHR wrapper do you use. Say for fetch() there is nice wrapper-mocker and libraries like axios also have their equivalence.
As for test itself. It does not work if you call a method and make expect on its result. It should be running method that must call server and then checking for mocked XHR if it has been called with valid parameters:
api.getUsersTotalPayout(userId);
expect(fetch_or_other_wrapper_for_mocking_xhr.get_last_request_method()).toEqual('get', endpoint, userId)
I have the following custom Axios instance:
import axios from 'axios'
export const BASE_URL = 'http://jsonplaceholder.typicode.com'
export default axios.create({
baseURL: BASE_URL
})
With the corresponding service:
import http from './http'
export async function fetchUserPosts(id) {
const reponse = await http.get(`/users/${id}/posts`)
return reponse.data
}
And this is the test for said service:
import moxios from 'moxios'
import sinon from 'sinon'
import http from '#/api/http'
import { fetchUserPosts } from '#/api/usersService'
describe('users service', () => {
beforeEach(() => {
moxios.install(http)
})
afterEach(() => {
moxios.uninstall(http)
})
it('fetches the posts of a given user', (done) => {
const id = 1
const expectedPosts = ['Post1', 'Post2']
moxios.stubRequest(`/users/${id}/posts`, {
status: 200,
response: expectedPosts
})
const onFulfilled = sinon.spy()
fetchUserPosts(1).then(onFulfilled)
moxios.wait(() => {
expect(onFulfilled.getCall(0).args[0].data).toBe(expectedPosts)
done()
})
})
})
Which when executed using Karma + Jasmine raises the following error:
Uncaught TypeError: Cannot read property 'args' of null thrown
What I would like to test is that when the endpoint /users/{id}/posts is hit a mocked response is sent back. All this while using my custom axios instance http.
I've tried stubbing as the first example of the documentation of moxios shows. However I don't think that fits my use case, as I would like to check that the request is formed correctly in my service.
I've also tried with the following code, which works as expected, however I would like to test my service (which the following code does not do):
import axios from 'axios'
import moxios from 'moxios'
import sinon from 'sinon'
describe('users service', () => {
beforeEach(() => {
moxios.install()
})
afterEach(() => {
moxios.uninstall()
})
it('fetches the posts of a given user', (done) => {
const id = 1
const expectedPosts = ['Post1', 'Post2']
moxios.stubRequest(`/users/${id}/posts`, {
status: 200,
response: expectedPosts
})
const onFulfilled = sinon.spy()
axios.get(`/users/${id}/posts`).then(onFulfilled)
moxios.wait(() => {
expect(onFulfilled.getCall(0).args[0].data).toBe(expectedPosts)
done()
})
})
})
Any ideas on how could I fix the error?
I know that is an old question but as I came across with it with the same problem, I'll post my approach:
You don't need to use sinon in this case, as you have an instance of axios, use it to configure moxios (as you already have done)
beforeEach(() => {
moxios.install(http)
})
afterEach(() => {
moxios.uninstall(http)
})
then you test your method like this:
it('test get', async () => {
const expectedPosts = ['Post1', 'Post2']
moxios.wait(() => {
const request = moxios.requests.mostRecent()
request.respondWith({ status: 200, response: expectedPosts }) //mocked response
})
const result = await fetchUserPosts(1)
console.log(result) // ['Post1','Post2']
expect(result).toEqual(expectedPosts)
})
That's it.
Regards