I want to configure a custom login command in which I have to make a signIn call which returns a promise.
commands.js:
Cypress.Commands.add("login", () => {
AuthLib.signIn(username, password).then((data) => {
cy.setLocalStorage("accessToken", data.accessToken);
});
AuthLib.signIn() returns a promise.
Then I want to use this command in an before block:
before(() => {
cy.login();
cy.saveLocalStorage();
});
I notice that the promise is not resolved.
A 'hacky' fix would be to add cy.wait(4000) between login() and saveLocalStorage().
But that makes my test depend on the loading time of the auth server
I found this 'related' issue: Cypress.io How to handle async code where https://www.npmjs.com/package/cypress-promise is referred. But this library cannot be used in before or beforeEach
How can I await the promise returned from login() / make sure the promise from login() is resolved before doing cy.saveLocalStorage()?
Update
I added the examples of what works and does not work in : https://github.com/Nxtra/Cypress-Amplify-Auth-Example/blob/main/cypress/support/commands.js
A solution would be to start with cy.then():
Cypress.Commands.add("login", () => {
cy.then(() => AuthLib.signIn(username, password)).then((data) => {
cy.setLocalStorage("accessToken", data.accessToken);
});
Make sure that you return that promise inside Cypress.Commands.add callback.
It's a bit confusing to deal with promises in Cypress context, since a lot of async behavior is magically handled within cy. commands.
Cypress.Commands.add("login", () => {
return AuthLib.signIn(username, password).then((data) => {
cy.setLocalStorage("accessToken", data.accessToken);
});
});
Other solution:
Cypress.Commands.add("login", () => {
return AuthLib.signIn(username, password);
});
before(() => {
cy.login();
cy.setLocalStorage("accessToken", data.accessToken);
});
Related
We have a function in our React project to fetch a list of stores. If the fetch takes longer than 3 seconds (set low for testing purposes) we abort the request and show an error.
const controller = new AbortController();
const getTimeout = setTimeout(() => controller.abort(), 3000);
const fetchStores = storeId => (
ourFetchStoresFunction(`http://my-api/${storeId}`, {
headers: { 'x-block': 'local-stores' },
signal: controller.signal
})
.then((results) => {
clearTimeout(getTimeout);
return results
})
.catch((err) => { throw err; })
);
I am trying to trigger the Abort error from Jest. I am using Mock Service Worker to intercept fetch requests and mock a delayed response:
import * as StoresAPI from '../Stores-api';
import { rest } from 'msw';
import { setupServer } from 'msw/node';
const server = setupServer(rest.get(`http://my-api/*`, (req, res, ctx) => {
console.log('TEST');
return res(
ctx.delay(5000),
ctx.status(200),
ctx.json({ stores: ['hi']})
)
}));
beforeAll(() => server.listen());
afterAll(() => server.close());
afterEach(() => server.resetHandlers());
it('fetchStores should return a stores array', async () => {
await StoresAPI.fetchStores(MOCK_STORES)
.then((stores) => {
expect(Array.isArray(stores)).toBe(true);
})
.catch();
});
When I run this, the delay works, it takes 5000 seconds for the mocked response to fire and the test to pass. But...The test passes and it seems abortController is never called. WHy is this happening? And is there a better way to test this (ideally without using MSW or other library)?
Your test is running synchronously; Jest runs all the code, which includes firing off a Promise but not awaiting it, and then finishes. After the test finishes, the Promise returns, but no one is waiting for it.
The code in the .then block is never even reached by Jest, since it is not awaited.
You can use async code inside Jest tests. I suspect this may give your more mileage:
// mock a quick response for this test
it('returns stores', async () => {
const stores = await StoresAPI.fetchStores(MOCK_STORES)
expect(stores).toEqual([/* returned array */])
})
// mock a long response for this test
it('times out', async () => {
await expect(() => StoresAPI.fetchStores(MOCK_STORES)).rejects.toThrow();
})
I have a function like this:
join(): void {
this.working.value = true;
if (this.info.value) {
axios.get('/url')
.then((result: ResultStatus) => {
this.result = result;
})
.catch((reason: AxiosError) => {
this.showError(AjaxParseError(reason));
})
.finally(() => {
this.working.value = false;
});
}
}
and I want to write some unit tests for this. The first unit test I want to write is to test that 'this.saving' is set to true so that I ensure my UI has a value it can use to show a loading indicator.
However, when I use jest to mock axios, jest resolves the axios promise immediately and I don't have a chance to test what happens before the then/finally block is called. Here is what my unit test code looks like:
import axios from 'axios';
jest.mock('axios');
const mockedAxios = axios as jest.Mocked<typeof axios>;
import successResponse from './__json__/LeagueJoinInfoSuccess.json';
describe('constructor:', () => {
let vm: classUnderTest;
beforeEach(() => {
vm = new classUnderTest();
mockedAxios.get.mockResolvedValue({ data: successResponse }); // set up the response
vm.join(); // the function under test
});
it('should set working true before calling the server to join', () => {
expect(vm.working.value).toBeTruthy();
});
it('should set working false after calling the server responds', async () => {
await flushPromises();
expect(vm.working.value).toBeFalsy();
});
});
The first expect statement is always false because the finally block is run before I have a chance to do an await flushPromises(); so the working value is always false.
Is there a convenient way to get jest's mock of axios to wait before resolving its promise?
UPDATE: Now here is a really strange thing: If I move the contents of BeforeEach into each of the tests, then it behaves the way that I am hoping it would behave. I guess I will open an issue over at jest and ask them what's going on.
I have a solution for you to create a promise as response, however, we're not gonna resolve it in the 1st test case to keep you test loading state then resolve it in the 2nd test as following:
describe('constructor:', () => {
let vm: classUnderTest;
// Resolve function
let resolveFn: (value?: unknown) => void
beforeEach(() => {
vm = new classUnderTest();
const promise = new Promise(resolve => {
// hold the resolve function to call in 2nd test
resolveFn = resolve;
});
mockedAxios.get.mockImplementation(() => promise);
vm.join(); // the function under test
});
it('should set working true before calling the server to join', () => {
expect(vm.working.value).toBeTruthy();
});
it('should set working false after calling the server responds', async () => {
// resolve the pending promise
resolve({
data: successResponse,
});
// I think you would wait the promise resolved
await new Promise(resolve => setTimeout(resolve));
expect(vm.working.value).toBeFalsy();
});
});
I'm using Jest to unit test a component that makes an Axios call in it's mounted() method. I'm mocking the Axios call so it returns a promise so the API call is not actually made. But the problem seems to be that because Axios is asynchronous, how do I tell my test to wait for the Axios call (even the fake one) to complete before running my expectations?
This does not work:
it('calls auth api', () => {
spyOn(axios, 'get').and.callFake(() => Promise.resolve().then(() => authResponse));
wrapper = shallowMount(App, {
localVue,
});
expect(axios.get).toHaveBeenCalledWith('api/auth');
// The "status" data is set in the axios callback. But this fails.
expect(wrapper.vm.status).toBe('AUTHORIZED');
});
If I wrap it in a timeout, it does work. I think I've read this is because the timeouts are always called after promises are resolved or something?
it('calls auth api', () => {
spyOn(axios, 'get').and.callFake(() => Promise.resolve().then(() => authResponse));
wrapper = shallowMount(App, {
localVue,
});
expect(axios.get).toHaveBeenCalledWith('api/auth');
setTimeout(() => {
expect(wrapper.vm.status).toBe('AUTHORIZED');
}, 0);
});
Is there a better way to do this?
Thanks!
Untested, but does the following not work?
const flushPromises = require('flush-promises');
it('calls auth api', async () => {
spyOn(axios, 'get').and.callFake(() => Promise.resolve().then(() => authResponse));
wrapper = shallowMount(App, {
localVue,
});
await flushPromises(); // <<
expect(axios.get).toHaveBeenCalledWith('api/auth');
});
(You will need to add https://www.npmjs.com/package/flush-promises (or write your own, it is about 4 lines of code, returning a resolved promise)
I have a problem that I can't understand and I was hoping that someone could help me with.
This is my test: state.messages is an empty array and api.botReply is called 0 times when it is in the function to be ran.
state.typing is set to true so I know I run the function.
test('test to resolve data from botReply', done => {
const wrapper = shallow(<Bot />);
api.botReply = jest.fn(() =>
Promise.resolve(wrapper.setState({ typing: false }))
);
wrapper.instance().sendReply();
setImmediate(() => {
wrapper.update();
console.log(wrapper.state('typing'));
console.log(wrapper.state('messages'));
expect(api.botReply).toHaveBeenCalledTimes(1);
done();
});
});
And this is the function that is run:
sendReply = () => {
this.setState({ typing: true });
api.botReply()
.then(reply => {
this.setState({ messages: [...this.state.messages, reply], typing: false });
})
};
Discarding promise chains and using random delays can lead to race conditions like this one.
Since a promise is provided in tests, it should be chained to maintain correct control flow. It's not a good practice to assign Jest spies as methods because they won't be cleaned up afterwards. A promise is supposed to resolve with reply, not set state.
It should be something like:
test('test to resolve data from botReply', async () => {
const wrapper = shallow(<Bot />);
const promise = Promise.resolve('reply')'
jest.spyOn(api, 'botReply').mockImplementation(() => promise);
wrapper.instance().sendReply();
expect(wrapper.state('typing')).toBe(true);
await promise;
expect(api.botReply).toHaveBeenCalledTimes(1);
expect(wrapper.state('typing')).toBe(false);
});
I have a API script in a file
const ApiCall = {
fetchData: async (url) => {
const result = await fetch(url);
if (!result.ok) {
const body = await result.text(); // uncovered line
throw new Error(`Error fetching ${url}: ${result.status} ${result.statusText} - ${body}`); // uncovered line
}
return result.json();
},
};
export default ApiCall;
When I mock the call, I have two uncovered lines in code coverage.
Any idea how can I make them cover as well.
Here is what I have tried so far which is not working
it('test', async () => {
ApiCall.fetchData = jest.fn();
ApiCall.fetchData.result = { ok: false };
});
I am kind of new into Jest, so any help would be great.
You need to provide a stubb response in your test spec so that the if statement is triggered. https://www.npmjs.com/package/jest-fetch-mock will allow you to do just that. The example on their npm page should give you what you need https://www.npmjs.com/package/jest-fetch-mock#example-1---mocking-all-fetches
Basically the result is stored in state(redux) and is called from there. jest-fetch-mock overrides your api call/route and returns the stored result in redux all within the framework.
Assuming that what you want to test is the ApiCall then you would need to mock fetch. You are mocking the entire ApiCall so those lines will never execute.
Also, you have an issue, because if you find an error or promise rejection, the json() won't be available so that line will trigger an error.
Try this (haven't test it):
it('test error', (done) => {
let promise = Promise.reject(new Error("test"));
global.fetch = jest.fn(() => promise); //You might need to store the original fetch before swapping this
ApiCall.fetchData()
.catch(err => );
expect(err.message).toEqual("test");
done();
});
it('test OK', (done) => {
let promise = Promise.resolve({
json: jest.fn(() => {data: "data"})
});
global.fetch = jest.fn(() => promise);
ApiCall.fetchData()
.then(response => );
expect(response.data).toEqual("data");
done();
});
That probably won't work right away but hopefully you will get the idea. In this case, you already are working with a promise so see that I added the done() callback in the test, so you can tell jest you finished processing. There is another way to also make jest wait for the promise which is something like "return promise.then()".
Plese post back