mockup axios with create and post, jestjs - javascript

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

Related

Undefined end-point http://localhost:8010/undefined/

I am trying to send data from front-end to back-end. Here is my front:
import axios from 'axios'
const api = axios.create({
baseURL: `${process.env.BASE_FRONT_URL}`, // process.env.BASE_FRONT_URL = http://localhost:8010
})
export const postTip = async (payload) => {
try {
const { data } = await api.post(`post-tip`, payload);
return data;
} catch (e) {
return [];
}
};
And here is back-end:
const router = require('express').Router();
const tipController = require('../controllers/tips/tipController')
router.post('post-tip', tipController.postTip);
That function tipController.postTip actually just receive and shows data, but when I trigger this end-point I get error: POST http://localhost:8010/undefined/post-tip 404 (Not Found). So, what's wrong with end-point and how can I make it work? Also, I have no idea, where does this undefined come from? Am I missing something?
I have found my mistake. Actually process.env.BASE_FRONT_URL was really undefined, so, I made it like that:
import axios from 'axios'
const api = axios.create({
baseURL: 'http://localhost:8084',
})
export const postTip = async (payload) => {
try {
const { data } = await api.post(`post-tip`, payload);
return data;
} catch (e) {
return [];
}
};
But the most important thing is that on back-end I have to use the same port (8084)

TypeError: axiosCookieJarSupport is not a function, works in Node.JS but not .vue pages?

I have a function to authenticate with a website, it works when I run it in a basic node.js script but it does not work when run from a .vue page (using NuxtJS framework).
When I run it in a .vue page it receives errors stating TypeError: axiosCookieJarSupport is not a function
Examples below.
Working code in basic .js file:
const axios = require("axios").default;
const axiosCookieJarSupport = require("axios-cookiejar-support").default;
const tough = require("tough-cookie");
const qs = require("qs");
async function main() {
let session = axios.create({
withCredentials: true,
baseURL: "xxx",
});
axiosCookieJarSupport(session);
session.defaults.jar = new tough.CookieJar();
let res = await session.post("/api/auth/login", qs.stringify({username: '', password: ''}))
.then((res) => {
console.log(res);
})
}
main();
Code in .vue page that is not working:
<script>
const axiosCookieJarSupport = require('axios-cookiejar-support').default
const tough = require('tough-cookie')
const qs = require('qs')
export default {
methods: {
async login () {
const session = this.$axios.create()
axiosCookieJarSupport(session) // <-- error occurs here
session.defaults.jar = new tough.CookieJar()
const res = await session.$post('/api/auth/login', qs.stringify({ username: '', password: '' }))
.then((res) => {
console.log(res)
})
}
}
}
</script>
I've tried moving the const axiosCookieJarSupport = require('axios-cookiejar-support').default into the function but it made no difference.
Any help would be much appreciated.
Because this library doesn't work in the browser:
Browser
Running on browser, this library becomes noop (config.jar
might be ignored).
https://github.com/3846masa/axios-cookiejar-support#browser
Fixed by updating my nuxt.config.js file with:
axios: {
credentials: true,
proxy: true,
jar: true // <-- this was missing
},
The code in the .vue page is now:
<script>
export default {
methods: {
async login () {
const qs = require('qs')
const session = this.$axios.create()
await session.$post('/api/auth/login', qs.stringify({ username: '', password: '' })).then((res) => {
console.log(res)
})
}
}
</script>
It appears to now be storing the session and I can use session on subsequent api calls.

How do I mock constructor state initialisation with Jest

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

Mocking Axios calls using Moxios in order to test API calls

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

How do I test axios in Jest?

I have this action in React:
export function fetchPosts() {
const request = axios.get(`${WORDPRESS_URL}`);
return {
type: FETCH_POSTS,
payload: request
}
}
How do I test Axios in this case?
Jest has this use case on their site for asynchronous code where they use a mock function, but can I do this with Axios?
Reference: An Async Example
I have done this so far to test that it is returning the correct type:
it('should dispatch actions with the correct type', () => {
store.dispatch(fetchPosts());
let action = store.getActions();
expect(action[0].type).toBe(FETCH_POSTS);
});
How can I pass in mock data and test that it returns?
Without using any other libraries:
import * as axios from "axios";
// Mock out all top level functions, such as get, put, delete and post:
jest.mock("axios");
// ...
test("good response", () => {
axios.get.mockImplementation(() => Promise.resolve({ data: {...} }));
// ...
});
test("bad response", () => {
axios.get.mockImplementation(() => Promise.reject({ ... }));
// ...
});
It is possible to specify the response code:
axios.get.mockImplementation(() => Promise.resolve({ status: 200, data: {...} }));
It is possible to change the mock based on the parameters:
axios.get.mockImplementation((url) => {
if (url === 'www.example.com') {
return Promise.resolve({ data: {...} });
} else {
//...
}
});
Jest v23 introduced some syntactic sugar for mocking Promises:
axios.get.mockImplementation(() => Promise.resolve({ data: {...} }));
It can be simplified to
axios.get.mockResolvedValue({ data: {...} });
There is also an equivalent for rejected promises: mockRejectedValue.
Further Reading:
Jest mocking documentation
A GitHub discussion that explains about the scope of the jest.mock("axios") line.
A related question which addresses applying the techniques above to Axios request interceptors.
Using jest functions like mockImplementation in TypeScript: Typescript and Jest: Avoiding type errors on mocked functions
I used axios-mock-adapter.
In this case the service is described in ./chatbot.
In the mock adapter you specify what to return when the API endpoint is consumed.
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
import chatbot from './chatbot';
describe('Chatbot', () => {
it('returns data when sendMessage is called', done => {
var mock = new MockAdapter(axios);
const data = { response: true };
mock.onGet('https://us-central1-hutoma-backend.cloudfunctions.net/chat').reply(200, data);
chatbot.sendMessage(0, 'any').then(response => {
expect(response).toEqual(data);
done();
});
});
});
You can see it the whole example here:
Service:
https://github.com/lnolazco/hutoma-test/blob/master/src/services/chatbot.js
Test:
https://github.com/lnolazco/hutoma-test/blob/master/src/services/chatbot.test.js
I could do that following the steps:
Create a folder __mocks__/ (as pointed by #Januartha comment)
Implement an axios.js mock file
Use my implemented module on test
The mock will happen automatically
Example of the mock module:
module.exports = {
get: jest.fn((url) => {
if (url === '/something') {
return Promise.resolve({
data: 'data'
});
}
}),
post: jest.fn((url) => {
if (url === '/something') {
return Promise.resolve({
data: 'data'
});
}
if (url === '/something2') {
return Promise.resolve({
data: 'data2'
});
}
}),
create: jest.fn(function () {
return this;
})
};
Look at this
The function to test album.js
const fetchAlbum = function () {
return axios
.get("https://jsonplaceholder.typicode.com/albums/2")
.then((response) => {
return response.data;
});
};
The test album.test.js
const axios = require("axios");
const { fetchAlbum } = require("../utils.js");
jest.mock("axios");
test("mock axios get function", async () => {
expect.assertions(1);
const album = {
userId: 1,
id: 2,
title: "sunt qui excepturi placeat culpa",
};
const payload = { data: album };
// Now mock axios get method
axios.get = jest.fn().mockResolvedValue(payload);
await expect(fetchAlbum()).resolves.toEqual(album);
});
I've done this with nock, like so:
import nock from 'nock'
import axios from 'axios'
import httpAdapter from 'axios/lib/adapters/http'
axios.defaults.adapter = httpAdapter
describe('foo', () => {
it('bar', () => {
nock('https://example.com:443')
.get('/example')
.reply(200, 'some payload')
// test...
})
})
For those looking to use axios-mock-adapter in place of the mockfetch example in the Redux documentation for async testing, I successfully used the following:
File actions.test.js:
describe('SignInUser', () => {
var history = {
push: function(str) {
expect(str).toEqual('/feed');
}
}
it('Dispatches authorization', () => {
let mock = new MockAdapter(axios);
mock.onPost(`${ROOT_URL}/auth/signin`, {
email: 'test#test.com',
password: 'test'
}).reply(200, {token: 'testToken' });
const expectedActions = [ { type: types.AUTH_USER } ];
const store = mockStore({ auth: [] });
return store.dispatch(actions.signInUser({
email: 'test#test.com',
password: 'test',
}, history)).then(() => {
expect(store.getActions()).toEqual(expectedActions);
});
});
In order to test a successful case for signInUser in file actions/index.js:
export const signInUser = ({ email, password }, history) => async dispatch => {
const res = await axios.post(`${ROOT_URL}/auth/signin`, { email, password })
.catch(({ response: { data } }) => {
...
});
if (res) {
dispatch({ type: AUTH_USER }); // Test verified this
localStorage.setItem('token', res.data.token); // Test mocked this
history.push('/feed'); // Test mocked this
}
}
Given that this is being done with jest, the localstorage call had to be mocked. This was in file src/setupTests.js:
const localStorageMock = {
removeItem: jest.fn(),
getItem: jest.fn(),
setItem: jest.fn(),
clear: jest.fn()
};
global.localStorage = localStorageMock;
New tools for testing have been introduced since the question was initially answered.
The problem with mocking is that you often test the mock and not the real context of your code, leaving some areas of this context untested.
An improvement over telling axios what promise to return is intercepting http requests via Service Workers.
Service worker is a client-side programmable proxy between your web app and the outside world. So instead of mocking promise resolution it is a more broader solution to mock the proxy server itself, intercepting requests to be tested. Since the interception happens on the network level, your application knows nothing about the mocking.
You can use msw (Mock Service Worker) library to do just that. Here is a short video explaining how it works.
The most basic setup I can think of is this:
1️⃣ set up handlers, which are similar to express.js routing methods;
2️⃣ set up mock server and pass handlers as it’s arguments;
3️⃣ configure tests to so that mock server will intercept our requests;
4️⃣ perform tests;
5️⃣ close mock server.
Say you want to test the following feature:
import axios from "axios";
export const fetchPosts = async () => {
const request = await axios.get("/some/endpoint/");
return {
payload: request,
};
};
Then test could look like this:
import { rest } from "msw";
import { setupServer } from "msw/node";
import fetchPosts from "./somewhere";
// handlers are usually saved in separate file(s) in one destined place of the app,
// so that you don't have to search for them when the endpoints have changed
const handlers = [ 1️⃣
rest.get("/some/endpoint/", (req, res, ctx) =>
res(ctx.json({ message: "success" }))
),
];
const server = setupServer(...handlers); 2️⃣
beforeAll(() => {
server.listen(); 3️⃣
});
describe("fetchPosts", () => {
it("should return 'success' message", async () => {
const resp = await fetchPosts();
expect(resp.payload?.data?.message).toEqual("success"); 4️⃣
});
});
afterAll(() => {
server.close(); 5️⃣
});
The configuration may be different depending on framework you are using. Some general examples for, among others, React (both REST and GraphQL) and Angular can be found on MSW’ repo. A Vue example is provided by VueMastery.
You can also find examples on MSW' recipes page.

Categories

Resources