I am using jest for unit testing in react native. Here is my componentWillMount() function:
componentWillMount() {
this.setState({ loading: true });
axios.get(SERVER_URL + '/courses')
.then(response => {
this.state.trainingcatalogues = []
this.state.traininglist = []
this.state.Location1 = []
this.state.Category1 = []
this.state.Location2 = ['All']
this.state.Category2 = ['All']
for (var key in response.data) {
if (response.data[key].coursetype === 'Instructor-Led' && response.data[key].status === 'Active' ) {
this.state.Location1.push(response.data[key].location);
this.state.Category1.push(response.data[key].category);
this.state.trainingcatalogues.push(response.data[key]);
this.state.traininglist.push(response.data[key]);
}
}
I want to mock the 'response' in my test case. It has the following format:
let response =
{
"data": [
{
"_id": "5acb16701e09ae8abc29e7fb",
"courseName": "Agile Fundamentals420",
"category": "Agile",
"coursetype": "Instructor-Led",
"duration": 22,
"addnote": "contractor",
"status": "Completed",
"registrations": 10
}
]
}
This is my unit test case:
it('should test the componentwillMount function', () => {
const wrapper = shallow(<Instructor_cata navigation = {navigation}/>);
const instance = wrapper.instance();
const componentWillMountSpy = jest.spyOn(instance,"componentWillMount");
instance.forceUpdate();
instance.componentWillMount();
expect(componentWillMountSpy).toHaveBeenCalled();
});
How do I mock this response data inside the unit test case?
You wil need to mock the Axios library, and there are several options to do so. First there are some libraries like axios-mock-adapter or jest-mock-axios, but you might as well just write a mock yourself, it is not much work.
Basic example:
// mock.js: custom axios mock file in your tests folder
module.exports = {
get: jest.fn((url) => {
switch (url): {
case '<your_url>/courses':
return Promise.resolve(<your_response_object>)
default:
return Promise.resolve({...})
})
}
};
Just import the mock in your test files. And you can use a similar approach for other axios methods like post().
Related
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 am writing unit tests for a pagination module, and it has a simple VueX store module.
I am using Vue.js 2.5 and Mocha/Chai/Sinon for tests. Setup is with Vue CLI 3.
The problem is that when the currentPage is incremented in the store in one unit test, this state persists into the next test even when I try to create a fresh store.
I have attempted to return a fresh pagination module by using a function that returns an Object.assign() fresh copy but this did not work. I have left this in the code as shown in the spec below.
store/pagination.js
const state = {
currentPage: 0
}
export const getters = {
currentPage: state => {
return state.currentPage
}
}
export const actions = {
nextPage ({ commit, state }) {
commit('setCurrentPage', state.currentPage + 1)
}
}
export const mutations = {
setCurrentPage (state, page) {
state.currentPage = page
}
}
export default {
namespaced: true,
state,
getters,
actions,
mutations
}
Pagination.spec.js
function getPaginationStore () {
return Object.assign({}, pagination)
}
describe('Paginate.vue', () => {
let localVue
let wrapper
let store
beforeEach(() => {
localVue = createLocalVue()
localVue.use(Vuex)
store = new Vuex.Store({
modules: {
pagination: getPaginationStore()
}
})
wrapper = shallowMount(Pagination, {
localVue,
propsData: {
items: [],
size: 24
},
store
})
})
afterEach(() => {
store = null
})
it('state should be 0', () => {
expect(wrapper.vm.pageNumber).to.equal(0)
wrapper.vm.$store.dispatch('pagination/nextPage')
expect(wrapper.vm.pageNumber).to.equal(1)
})
it('state should be 0 again but is 1', () => {
// THIS TEST FAILS. IT IS ACTUALLY 1
expect(wrapper.vm.pageNumber).to.equal(0)
})
})
The solution was to use a function for the state in the module rather than a plain old javascript object. Here is my new store state code:
export const state = () => {
return {
currentPage: 0
}
}
Answer was provided by #SumNeuron from the Vue discord channel.
I have a problem with my unit tests on server side.
My test is the next :
import { Meteor } from 'meteor/meteor';
import { Article } from '../../../imports/models/article';
import { Articles } from '../../../imports/collections/articles';
import './articles';
import { Random } from 'meteor/random';
import {Rate} from "../../../imports/models/rate.model";
import { expect, assert } from 'chai';
import {Observable} from "rxjs/Observable";
if (Meteor.isServer) {
describe('Articles', () => {
const userId = Random.id();
beforeEach(() => {
StubCollections.add([Articles]);
StubCollections.stub();
Articles.remove({});
});
it('can delete owned article', async (done) => {
const articleId = await Articles.insert({
title: "string",
content: "string",
owner: userId,
picture_url: "string",
source: "string",
createdAt: new Date()
}).toPromise();
const deleteArticle = Meteor.server.method_handlers["removeArticle"];
// // Run the method with `this` set to the fake invocation
//`enter code here`
const invocation = {userId};
deleteArticle.apply(invocation, [articleId]);
console.log(articleId);
const count = await Articles.find({}).count().toPromise();
// Verify that the method does what we expected
expect(count).equal(0);
StubCollections.restore();
done()
});
});
}
And I can't import stub-collection because typescript not found it.
I have try to had meteor server package on tsconfig.json but I did not suceed.
And when I delete StubCollection I have a Timeout of 2 seconde when "Articles.find({})"
Have you a idea for resolve it ?
My problems is with Article.find({}) result of type Observable with Meteor.observable.
My new test is the next
it('can delete owned article', async done => {
let fixFindToPromise: number = 0;
const articleId = await Articles.insert({
title: "string",
content: "string",
owner: userId,
picture_url: "string",
source: "string",
createdAt: new Date()
}).toPromise();
// console.log('A2', articleId2);
const deleteArticle = Meteor.server.method_handlers["removeArticle"];
// Run the method with `this` set to the fake invocation
deleteArticle.apply({userId}, [articleId]);
// Find the internal implementation of the task method so we can
console.log("ArticleId:", articleId);
Articles.find().subscribe((countLog) => {
fixFindToPromise++;
if (fixFindToPromise == 1 ) {
if ( countLog.length == 0 ) {
done();
} else {
done("Count not correct");
}
}
});
It's possible use find method with promise compatibilty result for better syntax and use assert and expect ?
And exists a project or solution for import package on typescript unit test server side ?
Thank for your response
I'm trying to write tests for a react component I've built that utilizes navigator.geolocation.getCurrentPosition() within a method like so (rough example of my component):
class App extends Component {
constructor() {
...
}
method() {
navigator.geolocation.getCurrentPosition((position) => {
...code...
}
}
render() {
return(...)
}
}
I'm using create-react-app, which includes a test:
it('renders without crashing', () => {
const div = document.createElement('div');
ReactDOM.render(<App />, div);
});
This test fails, printing out this in the console:
TypeError: Cannot read property 'getCurrentPosition' of undefined
I'm new to React, but have quite a bit of experience with angular 1.x. In angular it is common to mock out (within the tests in a beforeEach) functions, "services", and global object methods like navigator.geolocation.etc. I spent time researching this issue and this bit of code is the closest I could get to a mock:
global.navigator = {
geolocation: {
getCurrentPosition: jest.fn()
}
}
I put this in my test file for App, but it had no effect.
How can I "mock" out this navigator method and get the test to pass?
EDIT: I looked into using a library called geolocation which supposedly wraps navigator.getCurrentPosition for use in a node environment. If I understand correctly, jest runs tests in a node environment and uses JSDOM to mock out things like window. I haven't been able to find much information on JSDOM's support of navigator. The above mentioned library did not work in my react app. Using the specific method getCurrentPosition would only return undefined even though the library itself was imported correctly and available within the context of the App class.
It appears that there is already a global.navigator object and, like you, I wasn't able to reassign it.
I found that mocking the geolocation part and adding it to the existing global.navigator worked for me.
const mockGeolocation = {
getCurrentPosition: jest.fn(),
watchPosition: jest.fn()
};
global.navigator.geolocation = mockGeolocation;
I added this to a src/setupTests.js file as described here - https://create-react-app.dev/docs/running-tests#initializing-test-environment
I know this issue might have been solved, but seems that all the solutions above are all wrong, at least for me.
When you do this mock: getCurrentPosition: jest.fn()
it returns undefined, if you want to return something, this is the correct implementation:
const mockGeolocation = {
getCurrentPosition: jest.fn()
.mockImplementationOnce((success) => Promise.resolve(success({
coords: {
latitude: 51.1,
longitude: 45.3
}
})))
};
global.navigator.geolocation = mockGeolocation;
I am using create-react-app
A TypeScript version for anyone that was getting
Cannot assign to 'geolocation' because it is a read-only property.
In the mockNavigatorGeolocation.ts file (this can live in a test-utils folder or similar)
export const mockNavigatorGeolocation = () => {
const clearWatchMock = jest.fn();
const getCurrentPositionMock = jest.fn();
const watchPositionMock = jest.fn();
const geolocation = {
clearWatch: clearWatchMock,
getCurrentPosition: getCurrentPositionMock,
watchPosition: watchPositionMock,
};
Object.defineProperty(global.navigator, 'geolocation', {
value: geolocation,
});
return { clearWatchMock, getCurrentPositionMock, watchPositionMock };
};
I then import this in my test at the top of the file:
import { mockNavigatorGeolocation } from '../../test-utils';
And then use the function like so:
const { getCurrentPositionMock } = mockNavigatorGeolocation();
getCurrentPositionMock.mockImplementation((success, rejected) =>
rejected({
code: '',
message: '',
PERMISSION_DENIED: '',
POSITION_UNAVAILABLE: '',
TIMEOUT: '',
})
);
Mocking with setupFiles
// __mocks__/setup.js
jest.mock('Geolocation', () => {
return {
getCurrentPosition: jest.fn(),
watchPosition: jest.fn(),
}
});
and then in your package.json
"jest": {
"preset": "react-native",
"setupFiles": [
"./__mocks__/setup.js"
]
}
I followed #madeo's comment above to mock global.navigator.geolocation. It worked!
Additionally I did the following to mock global.navigator.permissions:
global.navigator.permissions = {
query: jest
.fn()
.mockImplementationOnce(() => Promise.resolve({ state: 'granted' })),
};
Set state to any of granted, denied, prompt as per requirement.
For whatever reason, I did not have the global.navigator object defined, so I had to specify it in my setupTests.js file
const mockGeolocation = {
getCurrentPosition: jest.fn(),
watchPosition: jest.fn(),
}
global.navigator = { geolocation: mockGeolocation }
Added to the above answers, if you want to update navigator.permissions, this will work.The key here is to mark writable as true before mocking
Object.defineProperty(global.navigator, "permissions", {
writable: true,
value: {
query : jest.fn()
.mockImplementation(() => Promise.resolve({ state: 'granted' }))
},
});