I am new to redux testing and have been trying to back fill test for an application I made so sorry if this is the complete wrong way to go about testing with nock and redux-mock-store.
//Action in authAction.js
export function fetchMessage() {
return function(dispatch) {
axios.get(ROOT_URL, {
headers: { authorization: localStorage.getItem('token') }
})
.then(response => {
console.log("hi")
dispatch({
type: FETCH_MESSAGE,
payload: response.data.message
});
})
.catch(response => {
console.log(response)
//callingRefresh(response,"/feature",dispatch);
});
}
}
This is the method and it seems to be getting called but normally goes to the catch cause of nock failing cause of the header not matching.
//authActions_test.js
import nock from 'nock'
import React from 'react'
import {expect} from 'chai'
import configureMockStore from 'redux-mock-store'
import thunk from 'redux-thunk'
const middlewares = [ thunk ]
const mockStore = configureMockStore(middlewares)
import * as actions from '../../src/actions/authActions';
const ROOT_URL = 'http://localhost:3090';
describe('actions', () => {
beforeEach(() => {
nock.disableNetConnect();
localStorage.setItem("token", '12345');
});
afterEach(() => {
nock.cleanAll();
nock.enableNetConnect();
});
describe('feature', () => {
it('has the correct type', () => {
var scope = nock(ROOT_URL).get('/',{reqheaders: {'authorization': '12345'}}).reply(200,{ message: 'Super secret code is ABC123' });
const store = mockStore({ message: '' });
store.dispatch(actions.fetchMessage()).then(() => {
const actions = store.getStore()
expect(actions.message).toEqual('Super secret code is ABC123');
})
});
});
});
Even when the header is removed and the nock intercepts the call. I am getting this error every time
TypeError: Cannot read property 'then' of undefined
at Context.<anonymous> (test/actions/authActions_test.js:43:24)
You're not returning the promise from axios to chain the then call onto.
Change the thunk to be like:
//Action in authAction.js
export function fetchMessage() {
return function(dispatch) {
return axios.get(ROOT_URL, {
headers: { authorization: localStorage.getItem('token') }
})
.then(response => {
console.log("hi")
dispatch({
type: FETCH_MESSAGE,
payload: response.data.message
});
})
.catch(response => {
console.log(response)
//callingRefresh(response,"/feature",dispatch);
});
}
}
You may also need to change the test so that it doesn't pass before the promise resolves. How to do this changes depending on which testing library you use. If you're using mocha, take a look at this answer.
Side note: I'm not sure if you have other unit tests testing the action creator separately to the reducer, but this is a very integrated way to test these. One of the big advantages of Redux is how easily each seperate cog of the machine can be tested in isolation to each other.
Related
I need to switch out my backend in-memory DB for testing due to memory issues. Below is my code
import { fireEvent, render, screen, waitFor } from "#testing-library/react";
import userEvent from "#testing-library/user-event";
import App from "App";
import axios from "axios";
import MockAdapter from "axios-mock-adapter";
import { AccessLevel, ResponseApi, SystemUserApi } from "types";
let mock: MockAdapter;
beforeAll(() => {
mock = new MockAdapter(axios);
});
afterEach(() => {
mock.reset();
});
beforeEach(() => {
jest.resetModules();
});
describe("<App />", () => {
test("login", async () => {
mock.onPost('/Hello').reply(200, getPost);
const result = render(<App />);
const user = userEvent.setup();
const btnLogin = screen.getByText(/Login/i) as HTMLButtonElement;
await userEvent.click(btnLogin);
let btnOk = screen.queryByText(/OK/i) as HTMLButtonElement;
expect(btnOk.disabled).toBe(true);
let btnCancel = screen.getByText(/Cancel/i) as HTMLButtonElement;
expect(btnCancel.disabled).toBe(false);
fireEvent.change(screen.getByLabelText(/Access Code/i) as HTMLInputElement, { target: { value: 'USER' } });
expect(btnOk.disabled).toBe(false);
await userEvent.click(btnOk);
//At this point I was expecting the onPost to be clicked
});
});
function getPost(config: any): any {
console.log(config);
debugger;
return {
data: {
access_code: 'USER'.toUpperCase(),
access_level: AccessLevel.USER ,
lock_level:true
} as SystemUserApi,
error: false,
} as ResponseApi
}
Deep down in the is a call axios post to /Hello but my function within the test is not called. I do not know if it has to do with the actual call being axios.request vs axios.post. I have tried switching to mock.onAny, but that did not seem to work. Not sure what to do here.
My component calls
this.axios.get()
when being mounted and passes a vuex-store variable to the api. The api returns an array as the response and the component displays some of the returned data after exchanging a loading-element with the real content.
In my unit test I want to simulate the result of the axios-request, wait for the transition between the loading- and the content-element and then finally check the validity of the content. However, the test fails and outputs:
Cannot read property 'get' of undefined
and highlights the get on this.axios.
Here is what I'm expecting to work (based on this guide):
... some imports etc. ...
const mockAxios = { whatIExpectToGet };
jest.mock("axios", () => ({
get: jest.fn(() => mockAxios)
}));
it("description of the test", async () => {
const wrapper = mount(MyComponent);
... code continues ...
Of course I'm accesssing axios via this and not directly like they do in the guide. But, since I can't find any mention of anything related to that, I assume that's irrelevant?
I also tried to mock axios myself like so:
... imports etc. ...
const axios = {
get: Promise.resolve({ whatIExpectToGet })
};
it("description of the test", async () => {
const wrapper = mount(MyComponent, {
global: {
mocks: [ axios ]
}
});
... code continues ...
Apparently people with similar problems used localVue.use() to inject stuff, but that's no longer supported.
Could someone be so kind and smart as to point me into the right direction, please?
Thank you.
-------------------> SOLUTION <-------------------
Thanks to tony 19 this question is already solved.
I ended up using an async function to mock axios because Promise.resolve() wasn't working for me:
import { shallowMount, flushPromises } from "#vue/test-utils";
import MyComponent from "#/components/MyComponent.vue";
describe("MyComponent.vue", () => {
const axios = {
get: async () => ({
data: { expectedData }
})
};
it("test description", async () => {
const wrapper = shallowMount(MyComponent, {
global: {
mocks: {
axios: axios
}
}
} as any);
expect(wrapper.html()).toContain("some_string_i_display_while_loading");
await flushPromises();
expect(wrapper.html()).toContain("some_string_i_display_after_getting_the_response");
});
});
Using global.mocks to mock axios is the right approach, but your attempt incorrectly used an array when it should've been an object:
const wrapper = mount(MyComponent, {
global: {
// mocks: [ axios ] ❌
mocks: { axios } ✅
}
})
Note axios.get() resolves to an axios.Response object, which stores the response data in its data property, so your mock should do the same.
Here's a full example:
// MyComponent.vue
export default {
mounted() {
this.axios.get('foo').then(resp => this.foo = resp.data)
}
}
// MyComponent.spec.js
it('gets foo', () => {
const wrapper = mount(MyComponent, {
global: {
mocks: {
axios: {
get: Promise.resolve({ data: { foo: true }})
// OR use an async function, which internally returns a Promise
get: async () => ({ data: { foo: true }})
}
}
}
}
})
I'm using Jest to test a function from a service that uses axios to make some api calls. The problem is that Jest keeps calling the actual services function instead of the mocked service function. Here is all of the code:
The tests:
// __tests__/NotificationService.spec.js
const mockService = require('../NotificationService').default;
beforeEach(() => {
jest.mock('../NotificationService');
});
describe('NotificationService.js', () => {
it('returns the bell property', async () => {
expect.assertions(1);
const data = await mockService.fetchNotifications();
console.log(data);
expect(data).toHaveProperty('data.bell');
});
});
The mock:
// __mocks__/NotificationService.js
const notifData = {
bell: false,
rollups: [
{
id: 'hidden',
modifiedAt: 123,
read: true,
type: 'PLAYLIST_SUBSCRIBED',
visited: false,
muted: false,
count: 3,
user: {
id: 'hidden',
name: 'hidden'
},
reference: {
id: 'hidden',
title: 'hidden',
url: ''
}
}
],
system: [],
total: 1
};
export default function fetchNotifications(isResolved) {
return new Promise((resolve, reject) => {
process.nextTick(() =>
isResolved ? resolve(notifData) : reject({ error: 'It threw an error' })
);
});
}
The service:
import axios from 'axios';
// hardcoded user guid
export const userId = 'hidden';
// axios instance with hardcoded url and auth header
export const instance = axios.create({
baseURL: 'hidden',
headers: {
Authorization:
'JWT ey'
}
});
/**
* Notification Service
* Call these methods from the Notification Vuex Module
*/
export default class NotificationService {
/**
* #GET Gets a list of Notifications for a User
* #returns {AxiosPromise<any>}
* #param query
*/
static async fetchNotifications(query) {
try {
const res = await instance.get(`/rollups/user/${userId}`, {
query: query
});
return res;
} catch (error) {
console.error(error);
}
}
}
I've tried a couple of variations of using require instead of importing the NotificationService, but it gave some other cryptic errors...
I feel like I'm missing something simple.
Help me please :)
The problem is that Jest keeps calling the actual services function instead of the mocked service function.
babel-jest hoists jest.mock calls so that they run before everything else (even import calls), but the hoisting is local to the code block as described in issue 2582.
I feel like I'm missing something simple.
Move your jest.mock call outside the beforeEach and it will be hoisted to the top of your entire test so your mock is returned by require:
const mockService = require('../NotificationService').default; // mockService is your mock...
jest.mock('../NotificationService'); // ...because this runs first
describe('NotificationService.js', () => {
it('returns the bell property', async () => {
...
});
});
I'm writing unit tests for a serverless application in TypeScript, and I'd like to mock the AWS SDK.
Unfortunately I have not found many existing type definitions for popular AWS mocking projects. In particular I'd like to use the aws-sdk-mock library, but without its type definitions I can't.
Theoretically I'd like to be able to do something like:
import 'jest';
import * as sinon from 'sinon';
import * as _ from 'lodash';
import { handler } from '../lib/lambda';
import AWSMock from 'aws-sdk-mock';
import { PutItemInput } from 'aws-sdk/clients/dynamodb';
const mockData: DataType = {
// ...some fields
};
describe('create data lambda tests', () => {
afterEach(() => {
sinon.restore();
AWSMock.restore();
});
it('returns a success response on creation', () => {
AWSMock.mock('DynamoDB.DocumentClient', 'put', (params: PutItemInput, callback: any) => {
return callback(null, 'Successful creation');
});
const mockGatewayEvent: any = {
headers: {
Authorization: // some JWT
},
body: _.clone(mockData)
};
handler(mockGatewayEvent).then((createdData: DataType) => {
expect(createdData.id).toBeDefined();
expect(createdData.id.length).toBeGreaterThan(0);
}, () => {
fail('The create request should not have failed');
});
});
});
Here's how we got it working with jest. This tests a lambda function that makes calls to Dynamo using the DynamoDB.DocumentClient.
The warnings about importing the aws-sdk-mock ts definitions go away for me if the file is called *.test.ts or *.spec.ts.
// stubbed.test.ts
// this line needs to come first due to my project's config
jest.mock("aws-sdk");
import * as AWS from "aws-sdk-mock";
import { handler } from "../index";
// these next two are just test data
import { mockDynamoData } from "../__data__/dynamo.data";
import { mockIndexData } from "../__data__/index.data";
describe("Stubbed tests", () => {
it("should return correct result when Dynamo returns one slice", async () => {
expect.assertions(2);
const mockQuery = jest.fn((params: any, cb: any) =>
cb(null, mockDynamoData.queryOneSlice)
);
AWS.mock("DynamoDB.DocumentClient", "query", mockQuery);
// now all calls to DynamoDB.DocumentClient.query() will return mockDynamoData.queryOneSlice
const response = await handler(mockIndexData.handlerEvent, null, null);
expect(mockQuery).toHaveBeenCalled();
expect(response).toEqual(mockIndexData.successResponseOneSlice);
AWS.restore("DynamoDB.DocumentClient");
});
});
I created an axios instance ...
// api/index.js
const api = axios.create({
baseURL: '/api/',
timeout: 2500,
headers: { Accept: 'application/json' },
});
export default api;
And severals modules use it ..
// api/versions.js
import api from './api';
export function getVersions() {
return api.get('/versions');
}
I try to test like ..
// Test
import { getVersions } from './api/versions';
const versions= [{ id: 1, desc: 'v1' }, { id: 2, desc: 'v2' }];
mockAdapter.onGet('/versions').reply(200, versions);
getVersions.then((resp) => { // resp is UNDEFINED?
expect(resp.data).toEqual(versions);
done();
});
Why resp is undefined?
Two things to try here:
Maybe you already have this elsewhere in your code, but be sure to set up mockAdaptor:
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
const mockAdapter = new MockAdapter(axios);
I haven't found a way to get the mock adapter working when the function you are testing uses 'axios.create' to set up a new axios instance. Try something along the lines of this instead:
// api/index.js
const api = {
get(path) {
return axios.get('/api' + path)
.then((response) => {
return response.data;
});
}
}
export default api;
For anyone still struggling with this.
You need to make sure you iniatilise your MockAdapter outside a test body.
ie.
❌ Incorrect ❌
it('should do a thing', () => {
const mockAdapter = new MockAdapter(axios);
})
✅ Correct ✅
const mockAdapter = new MockAdapter(axios);
it('should pass' () => {})
according to James M. advice, I updated my api/index.js , not using the axios.create...
api/index.js
import http from 'axios'
export default {
fetchShoppingLists: () => {
console.log('API FETCH SHOPPINGLISTS')
return http
.get('http://localhost:3000/shoppinglists')
.then(response => {
return response
})
.catch(error => {
console.log('FETCH ERROR: ', error)
})
}
}
You don't need axios-mock-adapter. Here is how I mock my axios:
// src/__mocks__/axios.ts
const mockAxios = jest.genMockFromModule('axios')
// this is the key to fix the axios.create() undefined error!
mockAxios.create = jest.fn(() => mockAxios)
export default mockAxios