TDD: Sinon 2.x and trying to test a sync method that uses async - unit-testing

So I've run into another snag, which I'm fighting with... I have a method that is a sync call, and within this method it calls a promise, async, method.
in my app I have the following:
export class App {
constructor(menuService) {
_menuService = menuService;
this.message = "init";
}
configureRouter(config, router) {
console.log('calling configureRouter');
_menuService.getById(1).then(menuItem => {
console.log('within then');
console.log(`configureRouter ${JSON.stringify(menuItem, null, 2)}`);
const collection = menuItem.links.map(convertToRouteCollection);
console.log(`collection ${JSON.stringify(collection, null, 2)}`);
//I think there is an issue with asyn to synch for the test
config.map(collection);
}).catch(err => {
console.error(err);
});
console.log('calling configureRouter assign router');
this.router = router;
}
}
The test I've tried the following within mocha
...
it('should update router config', function () {
const expectedData = {
name: "main menu",
links: [{
url: '/one/two',
name: 'link name',
title: 'link title'
}]
};
const configMapStub = sinon.stub();
const config = {
map: configMapStub
};
const routerMock = sinon.stub();
let app = null;
const actualRouter = null;
let menuService = null;
setTimeout(() => {
menuService = {
getById: sinon.stub().returns(Promise.resolve(expectedData).delay(1))
};
app = new App(menuService);
app.configureRouter(config, routerMock);
}, 10);
clock.tick(30);
expect(app.router).to.equal(routerMock);
expect(menuService.getById.calledWith(1)).to.equal(true);
//console.log(configMapStub.args);
expect(configMapStub.called).to.equal(true);
const linkItem = expectedData.links[0];
const actual = [{
route: ['', 'welcome'],
name: linkItem.name,
moduleId: linkItem.name,
nav: true,
title: linkItem.title
}];
console.log(`actual ${JSON.stringify(actual, null, 2)}`);
expect(config.map.calledWith(actual)).to.equal(true);
});
...
No matter what, I get configMockStub to always get false, while I am getting the menuService.getById.calledWith(1).to.equal(true) to equal true.
The test above was an attempt to try and get 'time' to pass. I've tried it without and have equally failed.
I'm really striking out on ideas on how to test this. Maybe I have the code wrong to reference a promise inside this method.
The only thing I can say I don't have any choice over the configureRouter method. Any guidance is appreciated.
Thanks!
Kelly

Short answer:
I recently discovered I was trying to make configureRouter method be a synchronous call (making it use async await keywords). What I found out was Aurelia does allow that method to be promised. Because of this, the test in question is no longer an issue.
Longer answer:
The other part of this is that I had a slew of babel issues lining up between babelling for mocha, and then babelling for wallaby.js. For some reason these two were not playing well together.
in the test above, another thing was to also change the following:
it('should update router config', function () {
to
it('should update router config', async function () {
I feel like there was another step, but at this time I cannot recall. In either case, knowing that I could use a promise made my world much easier for Aurelia.

Related

Jest - Cannot Change Context When Action is Dispatched

I am having a tough time writing a test for one feature. I am using context and useReducer.
The test is for a form. I need to change the field and then validate that the button is enabled. My problem is that on dispatch the state does not update. I'm thinking it is because I have to mock the implementation somehow for that action but not sure how to achieve this. Can someone please guide me in the right direction? Below is my whole test:
Below is my whole test
describe('Form Container', () => {
const mockUserState = {
user: {
firstName: '',
lastName: ''
}
}
const userContext = [
mockUserState,
{
userDispatch: jest.fn()
}
]
it('button in enabled on input value change', () => {
const wrapper = mount(
<UserProvider state={userContext}>
<FormContainer />
</UserProvider>
)
const submitBtn = wrapper.find('button[name="submitBtn"]');
expect(submitBtn.props()['disabled']).toBe(true);
const firstNameInput = wrapper.find('input[name="firstName"]');
firstNameInput.simulate('change', { target: { value: 'Test First Name'}});
expect(submitBtn.props()['disabled']).toBe(false);
});
})
After the dispatch I expect the button to be enabled but the following keeps failing.
expect(submitBtn.props()['disabled']).toBe(false);

Im trying to mock a function from a service but Jest keeps calling the actual function instead of the mock function

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 () => {
...
});
});

Vue with jest - Test with asynchronous call

How to make my test wait for the result of my api?
I'm using vue and jest to test my components.
I want to test the method that writes a client to my database. In my component I have the following method:
methods: {
onSubmitClient(){
axios.post(`urlApi`, this.dados).then(res => {
return res;
})
}
}
in my test
describe('login.vue', () => {
let wrapper
beforeAll(()=>{
wrapper = mount(client, {
stubs: ['router-link'],
store,
data() {
return {
dados: {
name: 'tes name',
city: 'test city'
},
};
}
})
})
it('store client', () => {
res = wrapper.vm.onSubmitLogin()
console.log(res);
})
})
My test does not wait for the API call to complete. I need to wait for the API call to know if the test worked. How can I make my test wait for API return?
There are several issues in your code.
First, you cannot return from an async call. Instead, you should be probably setting up some data in your onSubmitClient, and returning the whole axioscall, which is a Promise. for instance:
onSubmitClient(){
return axios.post(`urlApi`, this.dados).then(res => {
this.result = res;
return res;
})
}
I assume the method here is storing a result from the server. Maybe you don't want that; it is just an example. I'll come back to it later.
Ok, so now, you could call onSubmitClient in your wrapper and see if this.result is already set. As you already know, this does not work straightforward.
In order for a jest test to wait for asynchronous code, you need either to provide a done callback function or return a promise. I'll show an example with the former:
it('store client', (done) => {
wrapper.vm.onSubmitLogin().then((res) => {
expect(wrapper.vm.dados).toEqual(res);
done();
})
});
Now this code should just work, but still there is an issue with it, as #jonsharpe says in a comment.
You usually don't want to perform real network requests in unitary tests because they are slow and unrealiable. Also, unitary tests are meant to test components in isolation, and here we are testing not only that our component sets this.result properly when the request is made. We are also testing that there is a webserver up and running that is actually working.
So, what I would do in this scenario to test that single piece of functionality, is to extract the request to another method, mock it with vue-test-utils and jest.fn, and then assert that onSubmitClient does its work:
The component:
export default {
data() {
return {
http: axios,
...
},
methods: {
onSubmitClient(){
this.http.post(`urlApi`, this.dados).then(res => {
this.result = res;
})
}
}
}
}
The test:
it('store client', (done) => {
const fakeResponse = {foo: 'bar'};
var post = jest.fn();
var http : {
post,
};
var wrapper = mount(client, {
stubs: ['router-link'],
store,
data() {
return {
dados: {
name: 'tes name',
city: 'test city'
},
http, //now, the component under test will user a mock to perform the http post request.
}
}
});
wrapper.vm.onSubmitLogin().then( () => {
expect(post).toHaveBeenCalled();
expect(wrapper.vm.result).toEqual(fakeResponse);
done();
})
});
Now, your test asserts two things:
post gets called.
this.result is set as it should be.
If you don't want to store anything in your component from the server, just drop the second assertion and the this.result = res line in the method.
So basically this covers why your test is not waiting for the async request and some issues in your code. There are still some things to consider (f.i. I think a global wrapper is bad idea, and I would always prefer shallowMount over mount when testing components behavior), but this answer should help you a lot.
PS: didn't test the code, so maybe I messed up something. If the thing just doesn't work, look for syntax errors or similar issues.

How to "mock" navigator.geolocation in a React Jest Test

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

What is the point of unit testing redux-saga watchers?

In order to get 100% coverage of my Saga files I'm looking into how to test watchers.
I've been googling around, there are several answers as to HOW to test watchers. That is, saga's that do a takeEvery or takeLatest.
However, all methods of testing seem to basically copy the implementation. So what's the point of writing a test if it's the same?
Example:
// saga.js
import { delay } from 'redux-saga'
import { takeEvery, call, put } from 'redux-saga/effects'
import { FETCH_RESULTS, FETCH_COMPLETE } from './actions'
import mockResults from './tests/results.mock'
export function* fetchResults () {
yield call(delay, 1000)
yield put({ type: FETCH_COMPLETE, mockResults })
}
export function* watchFetchResults () {
yield takeEvery(FETCH_RESULTS, fetchResults)
}
Test method 1:
import { takeEvery } from 'redux-saga/effects'
import { watchFetchResults, fetchResults } from '../sagas'
import { FETCH_RESULTS } from '../actions'
describe('watchFetchResults()', () => {
const gen = watchFetchResults()
// exactly the same as implementation
const expected = takeEvery(FETCH_RESULTS, fetchResults)
const actual = gen.next().value
it('Should fire on FETCH_RESULTS', () => {
expect(actual).toEqual(expected)
})
})
Test method 2: with a helper, like Redux Saga Test Plan
It's a different way of writing, but again we do basically the same as the implementation.
import testSaga from 'redux-saga-test-plan'
import { watchFetchResults, fetchResults } from '../sagas'
import { FETCH_RESULTS } from '../actions'
it('fire on FETCH_RESULTS', () => {
testSaga(watchFetchResults)
.next()
.takeEvery(FETCH_RESULTS, fetchResults)
.finish()
.isDone()
})
Instead I'd like to simply know if watchFestchResults takes every FETCH_RESULTS. Or even only if it fires takeEvery(). No matter how it follows up.
Or is this really the way to do it?
It sounds like the point of testing them is to achieve 100% test coverage.
There are some things that you can unit test, but it is questionable if you should.
It seems to me that this situation might be a better candidate for an 'integration' test. Something that does not test simply a single method, but how several methods work together as a whole. Perhaps you could call an action that fires a reducer that uses your saga, then check the store for the resulting change? This would be far more meaningful than testing the saga alone.
I agree with John Meyer's answer that this is better suited for the integration test than for the unit test. This issue is the most popular in GitHub based on up votes. I would recommend reading it.
One of the suggestions is to use redux-saga-tester package created by opener of the issue. It helps to create initial state, start saga helpers (takeEvery, takeLatest), dispatch actions that saga is listening on, observe the state, retrieve a history of actions and listen for specific actions to occur.
I am using it with axios-mock-adapter, but there are several examples in the codebase using nock.
Saga
import { takeLatest, call, put } from 'redux-saga/effects';
import { actions, types } from 'modules/review/reducer';
import * as api from 'api';
export function* requestReviews({ locale }) {
const uri = `/reviews?filter[where][locale]=${locale}`;
const response = yield call(api.get, uri);
yield put(actions.receiveReviews(locale, response.data[0].services));
}
// Saga Helper
export default function* watchRequestReviews() {
yield takeLatest(types.REVIEWS_REQUEST, requestReviews);
}
Test example using Jest
import { takeLatest } from 'redux-saga/effects';
import { types } from 'modules/review/reducer';
import SagaTester from 'redux-saga-tester';
import MockAdapter from 'axios-mock-adapter';
import axios from 'axios';
import watchRequestReviews, { requestReviews } from '../reviews';
const mockAxios = new MockAdapter(axios);
describe('(Saga) Reviews', () => {
afterEach(() => {
mockAxios.reset();
});
it('should received reviews', async () => {
const services = [
{
title: 'Title',
description: 'Description',
},
];
const responseData = [{
id: '595bdb2204b1aa3a7b737165',
services,
}];
mockAxios.onGet('/api/reviews?filter[where][locale]=en').reply(200, responseData);
// Start up the saga tester
const sagaTester = new SagaTester({ initialState: { reviews: [] } });
sagaTester.start(watchRequestReviews);
// Dispatch the event to start the saga
sagaTester.dispatch({ type: types.REVIEWS_REQUEST, locale: 'en' });
// Hook into the success action
await sagaTester.waitFor(types.REVIEWS_RECEIVE);
expect(sagaTester.getLatestCalledAction()).toEqual({
type: types.REVIEWS_RECEIVE,
payload: { en: services },
});
});
});