I have the following example test:
import { assert } from 'chai'
function starWarsMovies () {
fetch('http://swapi.co/api/films/')
.then((res) => {
return res.json()
})
.then((res) => res.count)
}
describe('Get star war movies', () => {
it('should get 7', () =>{
assert.equal(starWarsMovies(), 7)
})
})
But I getting
ReferenceError: fetch is not defined
What do I have to use in order to test a fetch request.
UPDATE
I also tried:
import { polyfill } from 'es6-promise'
import fetch from 'isomorphic-fetch'
but then I get:
AssertionError: expected undefined to equal 7
and I don't understand why.
You are probably testing your code server-side with node.js.
fetch is not a part of node, but is a web-API, you get with newer browsers and can use from JavaScript running in a browser.
You need to import node-fetch as sketched below, and it will work:
npm install node-fetch --save
and in your code:
const fetch = require("node-fetch")
...
If you are running in (an older browser not supporting fetch) and is not using a tool like webpack, you must include the polyfills from your html the old "traditional way".
Even if you use node-fetch or isomorphic-fetch the issue here is that you're checking for equality between a number and the result of a function that doesn't return anything. I was able to make this work!
describe('Get star war movies', () => {
it('should get 7', async () => {
await fetch('http://swapi.co/api/films/')
.then((res) => {
return res.json()
})
.then((res) => {
console.log(res);
assert.equal(res.count, 7)
})
})
})
Note that I'm using async await syntax here. There are lots of different ways we could do this (callbacks, promises, async/await), but the key is that in making a call out to an API we have to wait for the result. Also, the response you get from the star wars API seems to be a huge object, so I took the liberty of assuming that you were just checking for the count!
Related
I'm trying to temporarily mock node-fetch in an ESM module while still retraining the original implementation so I can access a real endpoint's value. However, this errors with "Must use import to load ES Module." I recognize jest support for ESM is still pending - is there any way to have this behavior in a combination of current Node, ES6, and Jest?
worker.ts (dependency):
export default async () => {
const response = await fetch("http://example2.org");
return await response.json()
}
main.test.ts:
import { jest } from "#jest/globals";
jest.mock("node-fetch", () => {
return Promise.resolve({
json: () => Promise.resolve({ myItem: "abc" }),
})
})
import doWork from './worker.js';
import mockedFetch from 'node-fetch';
const originalFetch = jest.requireActual('node-fetch') as any;
test("Ensure mock", async () => {
const result = await doWork();
expect(result.myItem).toStrictEqual("abc");
expect(mockedFetch).toBeCalledTimes(1);
const response = await originalFetch("http://www.example.org");
expect(response.status).toBe(200);
const result2 = await doWork();
expect(result2.myItem).toStrictEqual("abc");
expect(mockedFetch).toBeCalledTimes(2);
});
First, Jest doesn't support jest.mock in ESM module for tests
Please note that we currently don't support jest.mock in a clean way
in ESM, but that is something we intend to add proper support for in
the future. Follow this issue for updates.
This is reasonable because import has different semantic than require. All imports are hoisted and will be evaluated at module load before any module code.
So in your case because you are using jest.mock I assume that your test code are transformed. In this case, if you want to use non "CommonJS" package, you should transform it too. You can change transformIgnorePatterns in jest config to []. It means that all packages from node_modules will go through transform. If it's too aggressive, you can pick specific modules which ignore like this "transformIgnorePatterns": [ "node_modules/(?!(node-fetch))" ] but don't forget about transitive dependencies. ;)
Add/change in jest config
"transformIgnorePatterns": []
jest.mock accept module factory which should returns exported code. In your case, you should write it like this.
jest.mock("node-fetch", () => {
return {
__esModule: true,
default: jest.fn(() =>
Promise.resolve({
json: () => Promise.resolve({ myItem: "abc" }),
})
),
};
});
If you have error
The module factory of jest.mock() is not allowed to reference any out-of-scope variables
just remove jest from import and use it as global variable or import it inside the module factory function
https://github.com/facebook/jest/issues/2567
Change const originalFetch = jest.requireActual('node-fetch') to
const originalFetch = jest.requireActual('node-fetch').default;
I have a module which configures axios:
// config/axiosConfig.js
import axios from 'axios';
const instance = axios.create({
baseURL: 'http://localhost:8080/api/v1'
});
export default instance;
And a module that uses this to make api calls:
// store/actions.ts
import axiosInstance from 'config/axiosConfig';
export const fetchUsers = () => (dispatch: ThunkDispatch<{}, {}, AnyAction>) => {
dispatch(loading(true));
return axiosInstance.get('/users')
.then(res => {
dispatch(loading(false));
dispatch(fetched(res.data));
})
.catch(err => dispatch(error(true)));
}
...
I want to mock the axios config file to test my api file. I've tried many many ways but nothing works. I thought it would be as simple as
// store/actions.test.ts
import axiosInstance from 'config/axiosConfig';
jest.mock('config/axiosConfig');
axiosConfig.get.mockResolvedValue({users: mockUserList});
...
But I guess that's not how it works.
Edit: The approach in my question works when I put axiosConfig.get.mockResolvedValue({users: mockUserList}); inside of the test, not right under the mock call.
Try this out (Put this at the top of the file or at the top inside beforeAll or beforeEach depending on what you prefer):
jest.mock('config/axiosConfig', () => ({
async get(urlPath) {
return {
users: mockUserList,
};
},
}));
This is a simple mock using a factory function. To use a mock everywhere, jest provides a better way to avoid repeating yourself. You create a __mocks__ directory and inside that, you can create your module and then override many of the builtins. Then, you can get away with just the following code.
// file.test.ts
jest.mock('fs')
// Rest of your testing code below
Take a look at the official docs to learn more about this.
If this doesn't work, then the module resolution setup in jest.config.js and tsconfig.js might be different.
Given I have an implementations files that looks something :
import ReactNative, { PushNotificationIOS, AsyncStorage } from 'react-native';
export function tryNotify() {
PushNotificationIOS.addEventListener('register', token => {
callback(token);
});
PushNotificationIOS.requestPermissions();
}
export function trySave(token) {
AsyncStorage.setItem('blah', token);
}
So if I want to write a test that spies on:
PushNotificationIOS.addEventListener.
However, I can't work out how to mock it, because as soon as I mock react-native...
describe('notify()', () => {
let generator;
beforeAll(() => {
jest.mock('react-native', () => ({
PushNotificationIOS: {
addEventListener: jest.fn(),
requestPermission: jest.fn(),
},
}));
});
afterAll(() => {
jest.unmock('react-native');
});
// No tests yet!
});
...I start getting the following error in my test:
Invariant Violation: Navigator is deprecated and has been removed from this package. It can now be installed and imported from `react-native-deprecated-custom-components` instead of `react-native`. Learn about alternative navigation solutions at http://facebook.github.io/react-native/docs/navigation.html
My best guess is I'm interfering with the inbuilt react-native mocks that jest provides:
The Jest preset built into react-native comes with a few defaults mocks that are applied on a react-native repository.
-jest docs
But I don't know where to look for to confirm this.
Does anyone have experience with this?
Thanks!
Edit
So I have two solutions:
AsyncStorage: the answer below works, as does this SO answer
PushNotificationsIOS: the answer below does not work for me, but this SO answer did
You can't jest.mock('react-native',... because react-native does some slightly nasty things with its exports, such that they can't be imported en-masse by jest or anything else.
You'll need to bypass this by targeting the module more directly:
jest.mock('react-native/Libraries/PushNotificationIOS', () => {})
In my Saga test for my react native application (that does work correctly) I have added the following test that calls a function that perform a POST http call (doScan).
describe('Scan the product and register the scanning action', () => {
const it = sagaHelper(scanProductSaga(scanProductAction(item)));
it('logscan via ASL', (result) => {
expect(result).toEqual(cps(ASLogger.logScan, xxx));
return logScanResult;
});
it('should register the product', (result) => {
expect(result).toEqual(call(doScan, logScanResult));
});
});
Separate file:
const doScan = scanObj =>
toJSON(fetch('https://xxxx.xxxxxx.com/logger/scans', {
method: 'POST',
headers: new Headers(CONTENT_TYPE_HEADERS),
body: JSON.stringify(scanObj),
}));
Note: the fetch function is from 'react-native-interfaces.js' within the react-native library. The test fails and the error is caused by the following exception :
ReferenceError: fetch is not defined
at doScan (/Users/andy/WebstormProjects/ASAP/api/index.js:81:11)....
What can cause such issue? What the solution may be?
react-native has fetch default, but test environment on node.js does not have fetch.
You can import fetch like following at the top of test code.
const fetch = require('node-fetch')
The file react-native-interface.js only declare the type of fetch.
declare var fetch: any;
For some cases, in a universal application for both, client and server the maintainers have to use isomorphic-fetch module to their Node project because of Node does not contain Fetch API yet. For more information read this question and answer
But in this special case, hence, React Native, it is some different, Because, in the mobile device area, there is no V8, SpiderMonkey or Node. There is JavaScriptCore. So, for this new situation should be used react-native-fetch.
It is a little different, install it with below code:
npm install react-native-fetch
And then, use it like a JSX component:
import Fetch from 'react-native-fetch'
...
<Fetch
url="https://jsonplaceholder.typicode.com/posts/1"
retries={3}
timeout={3000}
onResponse={async (res) => {
const json = await res.json()
console.log(json)
}}
onError={console.error}
/>
It's so new, but lovely.
I have several Redux-Thunk-style functions that dispatch other actions in one file. One of these actions dispatches the other as part of its logic. It looks similar to this:
export const functionToMock = () => async (dispatch) => {
await dispatch({ type: 'a basic action' });
};
export const functionToTest = () => async (dispatch) => {
dispatch(functionToMock());
};
In the case I'm actually running into, the functions are both much more involved and dispatch multiple action objects each. As a result, when I test my real-world functionToTest, I want to mock my real-world functionToMock. We already test functionToMock extensively, and I don't want to repeat the logic in those tests in functionToTest.
However, when I try that, like so:
import configureMockStore from 'redux-mock-store';
import thunk from 'redux-thunk';
jest.mock('../exampleActions');
const actions = require('../exampleActions');
const mockStore = configureMockStore([thunk]);
describe('example scenario showing my problem', () => {
test('functionToTest dispatches fuctionToMock', () => {
actions.functionToMock.mockReturnValue(() => Promise.resolve());
const store = mockStore({});
store.dispatch(actions.functionToTest());
expect(actions.functionToMock.mock.calls.length).toBe(1);
});
});
I get this error:
FAIL test.js
● example scenario showing my problem › functionToTest dispatches fuctionToMock
Actions must be plain objects. Use custom middleware for async actions.
at Error (native)
at dispatch (node_modules\redux-mock-store\dist\index-cjs.js:1:3137)
at Object.dispatch (node_modules\redux-thunk\lib\index.js:14:16)
at Object.<anonymous> (test.js:15:23)
(The example code I posted actually produces this error if you set them up in an environment with Jest, Redux, and Redux-Thunk. It is my MVCE.)
One thought I had is that I can move the two functions into different files. Unfortunately, doing so would break pretty dramatically with how the rest of our project is organized, so I'm not willing to do that unless it is truly the only solution.
How can I mock functionToMock in my tests for functionToTest without getting this error?
One solution is just to mock functionToMock. This question and its answers explain how to do so: How to mock imported named function in Jest when module is unmocked
This answer in particular explains that in order to get this approach to work when you're using a transpiler like Babel, you might need to refer to exports.functionToMock instead of functionToMock within functionToTest (outside of your tests), like so:
export const functionToTest = () => async (dispatch) => {
dispatch(exports.functionToMock());
};
Your example in the question helped me fix a separate bug. The solution was to toss thunk into const mockStore = configureMockStore([thunk]);. Thanks!