How to really call fetch in Jest test - javascript

Is there a way to call fetch in a Jest test? I just want to call the live API to make sure it is still working. If there are 500 errors or the data is not what I expect than the test should report that.
I noticed that using request from the http module doesn't work. Calling fetch, like I normally do in the code that is not for testing, will give an error: Timeout - Async callback was not invoked within the 5000ms timeout specified by jest.setTimeout. The API returns in less than a second when I call it in the browser. I use approximately the following to conduct the test but I also have simply returned the fetch function from within the test without using done with a similar lack of success:
import { JestEnvironment } from "#jest/environment";
import 'isomorphic-fetch';
import { request, } from "http";
jest.mock('../MY-API');
describe('tests of score structuring and display', () => {
test('call API - happy path', (done) => {
fetch(API).then(
res => res.json()
).then(res => {
expect(Array.isArray(response)).toBe(true);
console.log(`success: ${success}`);
done();
}).catch(reason => {
console.log(`reason: ${reason}`);
expect(reason).not.toBeTruthy();
done();
});
});
});
Oddly, there is an error message I can see as a console message after the timeout is reached: reason: ReferenceError: XMLHttpRequest is not defined
How can I make an actual, not a mocked, call to a live API in a Jest test? Is that simply prohibited? I don't see why this would fail given the documentation so I suspect there is something that is implicitly imported in React-Native that must be explicitly imported in a Jest test to make the fetch or request function work.

Putting aside any discussion about whether making actual network calls in unit tests is best practice...
There's no reason why you couldn't do it.
Here is a simple working example that pulls data from JSONPlaceholder:
import 'isomorphic-fetch';
test('real fetch call', async () => {
const res = await fetch('https://jsonplaceholder.typicode.com/users/1');
const result = await res.json();
expect(result.name).toBe('Leanne Graham'); // Success!
});
With all the work Jest does behind the scenes (defines globals like describe, beforeAll, test, etc., routes code files to transpilers, handles module caching and mocking, etc.) ultimately the actual tests are just JavaScript code and Jest just runs whatever JavaScript code it finds, so there really aren't any limitations on what you can run within your unit tests.

Related

Run Jest tests multiple times if it fails

I'm using Jest for testing some API endpoints, but these endpoints might fail falsy at some times, because of network issues and not my program bugs. So I want to do the test again multiple times (5 times for example) with a delay, and if all of these tries failed, Jest should report a failure.
What is the best way to achieve this? Does Jest or other libraries provide a solution? Or should I write my own program with something like setInterval?
For testing you should not ideally be making network calls as they slow down your tests. Moreover you might be communicating to an external API which ideally should not be part of your test code. Also can lead to false negatives as already observed by you. You can do something like below:
async function getFirstMovieTitle() {
const response = await axios.get('https://dummyMovieApi/movies');
return response.data[0].title;
}
For testing above network call you should mock your axios get request in your test. To test the above code, you should
jest.mock('axios');
it('returns the title of the first movie', async () => {
axios.get.mockResolvedValue({
data: [
{
title: 'First Movie'
},
{
title: 'Second Movie'
}
]
});
const title = await getFirstMovieTitle();
expect(title).toEqual('First Movie');
});
Try to read more about mocking in jest to understand better

How should I get API token before executing Unit Test cases?

Within my unit test cases, I'm trying to do unit tests against some data in the API therefore an API token is required. I'm hoping to find a way to call token API and store it in Redux before firing any API.
I'm aware of setup.js in Jest, tried calling my API there and store in Redux didn't work well. I don't think the setup.js waited the method to finish completely before starting the unit test.
// Within the Setup.js, I was calling method directly
const getAPItoken = async() => {
await getToken();
}
getAPItoken();
Currently I'm getting the API token in 1 of the Unit Test files. Upon the method completion, rest of the Unit Tests will run fine since they are getting the API token from Redux.
Sample of what I'm doing now
describe('Get API token', () => {
test('it should return true after getting token', async () => {
// Within the method itself, it actually store the token to redux upon receiving from API, also it will return TRUE upon success
const retrievedToken = await getToken();
expect(retrievedToken).toBeTruthy();
});
Is there a better way to handle this?
You can use globalSetup. It accepts an async function that is triggered once before all test suites.
So you can optain the API key and set it on node global object so you can access if from anywhere.
// setup.js
module.exports = async () => {
global.__API_KEY__ = 'yoru API key';
};
// jest.config.js
module.exports = {
globalSetup: './setup.js',
};

Jest spyOn call count after mock implementation throwing an error

I have a program that makes three post requests in this order
http://myservice/login
http://myservice/upload
http://myservice/logout
The code looks something like this
async main() {
try {
await this.login()
await this.upload()
} catch (e) {
throw e
} finally {
await this.logout()
}
}
where each method throws its own error on failure.
I'm using Jest to spy on the underlying request library (superagent). For one particular test I want to test that the logout post request is being made if the upload function throws an error.
I'm mocking the post request by throwing an exception.
const superagentStub = {
post: () => superagentStub
}
const postSpy = jest.spyOn(superagent, 'post')
.mockImplementationOnce(() => superagentStub)
.mockImplementationOnce(() => { throw new Error() })
.mockImplementationOnce(() => superagentStub)
const instance = new ExampleProgram();
expect(async () => await instance.main()).rejects.toThrow(); // This is fine
expect(postSpy).toHaveBeenNthCalledWith(3, 'http://myservice/logout')
If I don't mock the third implementation, the test will fail as logout() will throw its own error since the third post request will fail as a live call.
The spy in this case reports that only 1 call is made to the post method of the underlying library.
http://myservice/login
I find this strange because I am expecting 3 calls to the spy
http://myservice/login
http://myservice/upload -> but it throws an error
http://myservice/logout
Please keep in mind how to use expect(...).rejects.toThrow(). It's a bit tricky, though: https://jestjs.io/docs/expect#rejects
BTW: It's always nice to have ESLint active when coding with JavaScript. The following rule might then warn you about your error: https://github.com/jest-community/eslint-plugin-jest/blob/main/docs/rules/valid-expect.md ("Async assertions must be awaited or returned.")
Solution
You are missing an await in the beginning of the second-last line of your test-code. Replacing that line with the following should hopefully solve your problem:
await expect(() => instance.main()).rejects.toThrow();
which is the same as
await expect(async () => await instance.main()).rejects.toThrow();
You state // This is fine in your variant, but actually it isn't. You have a "false positive", there. Jest will most likely also accept the second-last line of your test even if you negate it, i.e. if you replace .rejects.toThrow() with .rejects.not.toThrow().
If you have more than one test in the same test-suite, Jest might instead state that some later test fails - even if it's actually the first test which causes problems.
Details
Without the new await in the beginning of the given line, the following happens:
expect(...).rejects.toThrow() initiates instance.main() - but doesn't wait for the created Promise to resolve or reject.
The beginning of instance.main() is run synchronously up to the first await, i.e. this.login() is called.
Mostly because your mockup to superagent.post() is synchronous, this.login() will return immediately. BTW: It might be a good idea to always replace async functions with an async mockup, e.g. using .mockResolvedValueOnce().
The Promise is still pending; JavaScript now runs the last line of your test-code and Jest states that your mockup was only used once (up to now).
The test is aborted because of that error.
The call to instance.main() will most likely continue afterwards, leading to the expected error inside instance.main(), a rejected Promise and three usages of your mockup - but all this after the test already failed.

Jest unit testing - Async tests failing Timeout

I got the following error message "Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL." On top of that, I also received an error message stating that 0 assertions were executed while expecting 2 assertions.
I've tried extending the timeout to 10 seconds using jest.setTimeout(10000), which should be more than sufficient time to execute that code, but the problem persisted.
I know m.employeeGetAll() works because when I test my web app using the browser, I can see the list of employees in the view.
Here's how my test looks like
it('Lists all employees successfully', () => {
expect.assertions(2);
return m.employeeGetAll().then(result => { //m.employeeGetAll() returns a promise
expect(result).toBeDefined();
expect(result.length).toBe(3);
});
});
The problem I've discovered, was the way async code works.
What cannot be seen in the code snippet was the call to mongoose.connection.close(); at the very end of my test file.
This call must be done inside afterEach() or afterAll() function for Jest unit testing framework. Otherwise, the connection to the database will be closed before the tests could complete since all of the calls in my controller methods are asynchronous; This leads to no promise ever being returned and the code goes into timeout.
Since I'm using beforeAll() and afterAll(), to load data from database once before the start of all tests and to clear the database at the end of all tests, I've included the call to connect to DB using mongoose inside beforeAll() as well.
Hope this helps someone who's also stuck in my situation.
with async you have to call done
it('Lists all employees successfully', (done) => {
expect.assertions(2);
return m.employeeGetAll().then(result => { //m.employeeGetAll() returns a promise
expect(result).toBeDefined();
expect(result.length).toBe(3);
done();
});
});

Mocking Request Header module using Jest

function createRequest(method) {
const init = {
method,
headers: new Headers({.....}),
};
return new Request(url, init); }
I am using Request headers (with Fetch) in the above code (https://davidwalsh.name/fetch )
However while writing unit test cases using Jest, it gives me this error: ReferenceError: Headers is not defined
DO I need to mock even these standard modules? How should I import Headers in unit test cases
I say yes, mocking Headers is definitely an option in a testing context.
In my particular case, I have simply mocked it like so:
global.Headers = ()=>{}
This will work just fine if you want to test that your code is behaving properly based on the response returned by fetch.
If you also need to check that the correct headers are sent, you would need a more sophisticated mock, and/or perhaps a specialized test suite for your networking methods.
adding ' import "isomorphic-fetch" ' to the jest setup file should resolve this issue as this will make the missing dom APIs available in the tests
I know this is an old question, but for anyone who runs into this (as I did), this worked for me:
// before running each test
beforeEach(() => {
// define `append` as a mocked fn
const append = jest.fn();
// set test `Headers`
global.Headers = () => ({
append: append,
});
});

Categories