I am needing to spyOn window.location.assign for my unit test. But when I run the test I get this error.
Cannot spy the assign property because it is not a function; undefined given instead
Here is my code:
jest.spyOn(window.location, "assign");
Could anyone give me some hints or solutions on this case?
Since Jest v25 (Which uses a newer version of JSDOM) you will get the following error:
TypeError: Cannot assign to read only property 'assign' of object '[object Location]'
This is not a Jest/JSDOM bug by the way. This is normal browser behaviour and JSDOM tries to act like a real browser.
A workaround is to remove the location object, create your own one and after running your tests you should reset it to the original location object:
describe('My awesome unit test', () => {
// we need to save the original object for later to not affect tests from other files
const realLocation = global.location
beforeAll(() => {
delete global.location
global.location = { assign: jest.fn() }
// or even like this if you are also using other location properties (or if TypeScript complains):
// global.location = { ...realLocation, assign: jest.fn() }
})
afterAll(() => {
global.location = realLocation
})
it('should call location.assign', () => {
// ...your test code
expect(global.location.assign).toHaveBeenCalled()
// or even better:
// expect(global.location.assign).toHaveBeenCalledWith('/my_link')
})
})
As window can only be accessed through the global keyword in jest tests and window.location.assign is not implemented in jsdom, you can try
jest
.spyOn(global.location, "assign")
.mockImplementation(url => console.log(url))
I have a Vue component/view that performs an API request using Axios and updates the component data with the response. I'm using Moxios to mock the Axios request in unit tests.
I tried using Vue.nextTick to postpone assertion of the updated data, but the component has not updated at that point yet. If I add a delay, the assertion works correctly:
setTimeout(() => {
expect(wrapper.text()).toMatch('Updated text')
done()
}, 500)
However this is bad practice, slows down the tests and is a race condition.
Is there some kind of assertion check that would be called every time a component updates? Essentially, I'm looking for something like:
Vue.eventually(() => {
expect(wrapper.text()).toMatch('Updated text')
done()
})
A generic (non-Vue-specific) workaround:
const test = () => {
try {
expect(wrapper.text()).toMatch('Updated text')
done()
} catch (e) {
setTimeout(test, 1)
}
}
setTimeout(test, 1)
However, any failures from the expect are ignored and the test times out without any message if failing.
I'm using axios 0.18.0, jest 22.4.2 and I'm trying to test a simple request using Axios and Jest based on the Axios documentation example and Jest Async / Await documentation
// services.js
import axios from 'axios';
export const getUser = () => axios.get('https://api.github.com/users/mzabriskie');
// services.spec.js
import { getUser } from './services';
it('should return data from github user', async () => {
jasmine.DEFAULT_TIMEOUT_INTERVAL = 30000;
const result = await getUser();
console.log('RESULT -->', result);
});
Once the DEFAULT_TIMEOUT_INTERVAL is passed (in this example 30 seconds), the test gives the following error message:
Error: Timeout - Async callback was not invoked within timeout
specified by jasmine.DEFAULT_TIMEOUT_INTERVAL
And it never reaches the console.log statement.
Any idea what I might be missing here or how to debug this?
So it turns out that the problem was using the dependency jest-mock-axios which it seems once it's configured it never allows HTTP requests to go through in the tests, even if you are not using it in a specific test (like in the original question example).
So in order to fix this all related requests tests must have defined mocks in order to work (or else remove the jest-mock-axios dependency entirely if you wish to test real API requests).
In React Native I use fetch to perform network requests, however fetch is not an explicitly required module, so it is seemingly impossible to mock in Jest.
Even trying to call a method which uses fetch in a test will result in:
ReferenceError: fetch is not defined
Is there a way to test such API requests in react native with Jest?
Inside your test case you can mock any function you want by using Jest's mocks:
fetch = jest.fn(() => Promise.resolve());
This approach works only for the promise-based test cases (see pit in the Jest docs).
As far as fetch is an async function, you need to run all your tests using pit (read more about async tests here).
Another approach where you mock the global fetch object:
const mockSuccesfulResponse = (
status = 200,
method = RequestType.GET,
returnBody?: object
) => {
global.fetch = jest.fn().mockImplementationOnce(() => {
return new Promise((resolve, reject) => {
resolve({
ok: true,
status,
json: () => {
return returnBody ? returnBody : {};
},
});
});
});
};
The above helper method can be modified any way you want :-) Hope it helps someone
Rather than rolling your own mock, you can use the jest-fetch-mock npm package to override the global fetch object. That package allows you to set up fake responses and verify sent requests. See that link for extensive usage examples.
I solved this by adding isomorphic-fetch.
$ npm install --save isomorphic-fetch
and using it like
import fetch from 'isomorphic-fetch';
...
fetch('http://foo.com');
whatwg-fetch might work as well
Suppose you want to test resolve and reject cases, for this first you mock the fetch behaviour and then use Jest's rejects and resolves methods with with assertion block
function fetchTodos() {
return fetch(`${window.location.origin}/todos.json`)
.then(response => response.json())
.catch(error => console.log(error))
}
describe('fetchTodos', () => {
it('returns promise resolving to parsed response', () => {
global.fetch = jest.fn(() => Promise.resolve({ json: () => ''}))
expect(fetchTodos()).resolves.toBe('');
})
it('returns promise handling the error', async () => {
global.fetch = jest.fn(() => Promise.reject(''))
expect(fetchTodos()).rejects.toBe('')
})
})
As #ArthurDenture recommended, you can use fetch-mock, but there are some additional packages you will need to install to make it work with React Native and Jest:
$ npm install --save-dev fetch-mock
$ npm install --save-dev babel-plugin-transform-runtime
$ npm install --save-dev babel-preset-env
You can then mock fetch requests in your tests. Here is an example:
// __tests__/App.test.js
import React from 'react';
import App from '../App';
import fetchMock from 'fetch-mock';
import renderer from 'react-test-renderer';
it('renders without crashing', () => {
fetchMock.mock('*', 'Hello World!');
const rendered = renderer.create(<App />).toJSON();
expect(rendered).toBeTruthy();
});
Due to problems using fetch-mock with jest, I've release fetch-mock-jest. It basically gives the full fetch-mock api, but with a few jest-specific helpers, and works out of the box with jest, without needing to do any tricky wiring yourself
As shown in the react-testing-library documentation, you can use the jest.spyOn() function, which will mock the fetch function only for the next time it is called.
const fakeUserResponse = {token: 'fake_user_token'}
jest.spyOn(window, 'fetch').mockImplementationOnce(() => {
return Promise.resolve({
json: () => Promise.resolve(fakeUserResponse),
})
})
react-testing-library
I'm trying to write a unit test for a controller that uses simple-auth authentication in an ajax call. Assertion tests work great but the session property does not appear to be defined in the unit test module scope.
Example action in controller:
authenticate() {
let credentials = this.getProperties('identification', 'password');
this.get('session').authenticate('simple-auth-authenticator:token', credentials)
.then(() => {
this.transitionToRoute('index');
}, (error) => {
this.set('errorMessage', error.error);
});
}
Example test:
it('should not authenticate', function () {
let controller = this.subject();
controller.send('authenticate');
expect(controller.get('errorMessage')).to.equal("Invalid email/password combination");
});
Session is undefined error message:
TypeError: Cannot read property 'authenticate' of undefined
at authenticate (http://localhost:7357/assets/app.js:587:28)
at mixin.Mixin.create.send (http://localhost:7357/assets/vendor.js:37164:54)
at Context.<anonymous> (http://localhost:7357/assets/app.js:2002:18)
at Context.wrapper (http://localhost:7357/assets/test-support.js:1756:27)
at invoke (http://localhost:7357/assets/test-support.js:13772:21)
at Context.suite.on.context.it.context.specify.method (http://localhost:7357/assets/test-support.js:13837:13)
at Test.require.register.Runnable.run (http://localhost:7357/assets/test-support.js:7064:15)
at Runner.require.register.Runner.runTest (http://localhost:7357/assets/test-support.js:7493:10)
at http://localhost:7357/assets/test-support.js:7571:12
at next (http://localhost:7357/assets/test-support.js:7418:14)
In unit tests you don't have a running application so injections etc. that happen in initializers aren't run. The best way to make sure the session exists in the controller would be to stub it which would also make it easy to make sure it behaves as you want it to behave in your test.
The alternative would be to turn the unit test into an acceptance test - in that case you have an initialized app that the test runs with and the session will be available in the controller already.