I've successfully mocked several routes in testing this application. My recent work involved making a GET request on the app's startup.
export const requestSettings = () =>
dispatch => {
console.log('Requesting settings');
const url = urls.SETTINGS_HTTP;
axios.get(url)
.then(res => {
console.log('response from axios');
return dispatch(updateSettingsFromBackend(res.data));
})
.catch((e) => {
console.log('axios error', e, e.config);
return dispatch(reportError('Request for initial settings failed.'));
});
};
In Cypress I mocked this route which allowed all my tests to run (since otherwise they'd fail on the failed HTTP request at the start of each test):
// commands.js
const urls = buildUrls(Cypress.env()); // we don't have the window yet
const settingsHttpUrl = urls.SETTINGS_HTTP;
const initialSettingsForTest = { ...defaultSettingsState, displayProgressIndicator: true };
const initialPayloadForTests = _createSettingsApiPayload(initialSettingsForTest);
Cypress.Commands.add("mockGetSettings", (code: number = 200) =>
cy.route({
method: 'GET',
url: settingsHttpUrl,
status: code,
response: code === 200 ? initialPayloadForTests : {},
}));
Cypress.Commands.add("mockPutSettings", (code: number) =>
cy.route({
method: 'PUT',
url: settingsHttpUrl,
status: code,
response: {},
}));
// the tests
describe('updating settings (requestParameterChange)', () => {
beforeEach(() => {
cy.mockGetSettings(200);
cy.visitSettings();
});
...
This worked fine.
My latest branch involves waiting to make this initial request until a certain websocket message is received. In my cypress tests I am able to dispatch that websocket handler to get the sequence going and ultimately call the same GET endpoint. With my back-end running, I am able to do this in the live app. The implementation is there.
However, in this branch, my mock is just not working! With the back-end up, the functionality works (which means it's hitting the real back-end, not the mock), and with the back-end down, it fails (because the mock isn't being hit).
describe('Settings Page', () => {
beforeEach(() => {
cy.server();
cy.clock();
});
describe('initial state', () => {
beforeEach(() => {
cy.mockGetSettings(200).as('getRequest');
cy.visitSettings();
});
it('starts in a waiting state, with no settings.', () => {
cy.contains('Waiting for settings...');
});
it.only('requests the settings when it receives a FIRMM status message', () => {
const message = { data: JSON.stringify({ status: 10 }) } as MessageEvent;
cy.dispatch(handleStatusMessage(message));
cy.wait('#getRequest');
});
(Note, by the way, the mockPutSettings command earlier in this post, which DOES WORK.)
cy.wait('#getRequest') fails with
CypressError: Timed out retrying: cy.wait() timed out waiting 5000ms for the 1st request to the route: 'getRequest'. No request ever occurred.
Chrome console shows: GET http://localhost:5000/settings/ net::ERR_EMPTY_RESPONSE
In fact, taking the app itself out of the equation also fails:
it('requests the settings when it receives a FIRMM status message', () => {
cy.visit(url);
cy.wait('#getRequest');
});
In the cypress window, we can see that the route is being created/set up, but it doesn't get hit.
What's happening?
PS. axios-mock-adapter DOES work for this:
function mockGet() {
// ... setup of constants as before ...
return new MockAdapter(axios).onGet(settingsHttpUrl).reply(() => {
console.log('MOCK AXIOS HIT');
return [200, initialPayloadForTests];
});
}
describe('Settings Page', () => {
beforeEach(() => {
cy.server();
cy.clock();
mock = mockGet();
});
afterEach(() => {
mock.restore();
});
The tests work, the response comes, MOCK AXIOS HIT prints to the console. However, mock.history.get is empty.
Related
Am using server sent events in an express server like this;
const sendEventDashboard = async (req, res) => {
try {
const orders = await Order.find({ agent_id: req.params.id })
.populate("agent_id")
.sort({ _id: -1 });
res.writeHead(200, {
"Cache-Control": "no-cache",
"Content-Type": "text/event-stream",
Connection: "keep-alive",
});
const sseId = new Date().toDateString();
const intervalId = setInterval(() => {
writeEvent(res, sseId, JSON.stringify(orders));
}, SEND_INTERVAL);
res.on("close", () => {
clearInterval(intervalId);
res.end();
// console.log("Client closed connection browser");
});
} catch (error) {
console.log(error);
}
};
export const getOrdersStreamDashboard = async (req, res) => {
if (req.headers.accept === "text/event-stream") {
sendEventDashboard(req, res);
} else {
res.json({ message: "Okay" });
}
};
and this is how i use it in a react app using a useEffect hook;
useEffect(() => {
const es = new EventSource(
`${process.env.REACT_APP_SERVER_URL}/weborders/${agentId}/stream_dashboard`
);
es.addEventListener("open", () => {
console.log("Dashboard stream opened!");
});
es.addEventListener("message", (e) => {
const data = JSON.parse(e.data);
setTrackOrderCount(data);
});
return () => {
// es.removeAllEventListeners();
es.close();
es.removeEventListener("message", (e) => {
const data = JSON.parse(e.data);
setTrackOrderCount(data);
});
};
}, [trackOrderCount]);
Everything runs as desired apart from event source always running until when the app/browser crushes. I get no error when it stops running and have to refresh for it to start again. This happens like after 10mins of inactivity or being on that same page for a long duration. Is there a way I can only run sse only when the state in the server is different from that of the client because i think the browser crushes because server sent events continuously run even when there's no event. I tried to remove the dependency array [trackOrderCount] in the useEffect and the setInterval in the server but that didn't solve the issue.
The solution might be in comparing the local and global versions before the event is sent but i've failed to figure out where to put that logic! I the browser's console, this is what i get;
and this will run for sometime then crush!
I've got a saga that has some error handling logic in it - I want to test that a call is made three times and provide a response for each invocation.
The use case is that the saga retries on the first two errors before giving up, so I need a sequence of response: [fail, fail, success]
it("must succeed after the first two requests are failures", () =>
expectSaga(
sagaToTest
).provide([
[
call(getdata, request),
throwError(new Error("oops")) // do this twice and succeed on the third invication
]
])
.call(getdata, request)
.call(getdata, request)
.call(getdata, request)
.put(actions.itSucceeded("message"))
.run());
});
This is straightforward in other testing / mocking libraries but for some reason I can't seem to find the right documentation.
Thanks!
This library does exactly that https://www.npmjs.com/package/saga-test-stub
You'll need to split your code tho, first encapsulate the throwable call in a separate saga and test it
function* callApi(request: any){
try {
const response = call(getdata, request);
return {sucess:true,response}
}
catch (e){
return {sucess:false}
}
}
describe('callApi saga', () => {
let sagaStub: SagaStub;
beforeEach(() => {
sagaStub = stub(callApi, {});
});
describe('when call to api fails', () => {
beforeEach(() => {
jest.spyOn(api,'callApi').mockImplementation(()=> {
throw new Error()
});
it('should return success false', () => {
expect(saga).toBeDone({sucess:false})
});
});
});
describe('when call to api works', () => {
// ...
});
});
then stub the yielded values from the first saga
describe('sagaToTest', () => {
let sagaStub: SagaStub;
beforeEach(() => {
sagaStub = stub(sagaToTest, {});
when(sagaStub).yields(call(callApi,{})).doNext(
{succes: false},
{succes: false},
{succes: true, response: 'here you go'},
)
});
it('must succeed after the first two requests are failures', () => {
expect(sagaStub).toYield(
call(callApi,{}), //note: this is redundant since it is stubbed
call(callApi,{}), //note: this is redundant since it is stubbed
call(callApi,{}), //note: this is redundant since it is stubbed
put(actions.itSucceeded("message"))
)
});
});
I am calling an API using fetch and writing test cases for that. While making the Fetch call, I am expected mocked data but getting API error message.
Please help me to know why its not mocking the data.
Using Jest, jest-fetch-mock modules. Code is as follow
const login = (username, password) => {
return fetch('endpoint/login', () => {
method: 'POST',
body: JSON.stringify({
data :{
username,
password
}
})
})
.then (res => res.json())
.then (data => {
if(data.response){
return {
response: "accessToken",
error: null
}
}else{
return {
response: null,
error: "Something went wrong"
}
}
})
}
Now I am writing Unit Test to test this api, as below :-
test("Auth Success Scenario", async () => {
const onResponse = jest.fn();
const onError = jest.fn();
fetchMock.mockResponseONce(JSON.stringify({
data: {
response: "accessToken",
error: null
}
}));
return await AuthService.login("andy","password")
.then(onResponse)
.catch(onError)
.finally( () => {
console.log(onResponse.mock.calls[0][0]) // its return API error not mocked data
})
})
It was meant to be comment, sadly I don't have enough reputation.
Have you enabled jest mocks as specified in the documentation link
Create a setupJest file to setup the mock or add this to an existing setupFile. :
To setup for all tests
require('jest-fetch-mock').enableMocks()
Add the setupFile to your jest config in package.json:
"jest": {
"automock": false,
"setupFiles": [
"./setupJest.js"
]
}
Because that seems to be the only case, in which fetch will try to make actual call to the API, instead of giving mocked response, thus causing failure.
You can even enable mocks for specific test file as well
import fetchMock from "jest-fetch-mock";
require('jest-fetch-mock').enableMocks();
I am trying to write a test using jest and react-testing-library for a component. The test needs to wait for useEffect to update the state following an axios request. I'm using moxios to mock the api call but I can't get the test to wait for moxios to return before it fires a click event handler that again sends a delete request to the api. The test fails saying that the DOM element I'm trying to click on doesn't exist yet because it's only produced once the useEffect request updates.
I've tried using flushEffect to wait and I've also tried wrapping the click inside the initial moxios request but both don't work
I've cut out any code that is not relevant.
This is the component. Once loaded it sends a get request to an api to grab a json response of some benefits. The BenefitsTable component takes in the benefits and for each one produces a table which includes a delete button. I'm trying to first load in the benefits and then click on the delete button. No delete button exists before they are loaded.
const Benefits = props => {
const [benefits, setBenefits] = useState([])
const [editing, setEditing] = useState(false)
const [editingBenefit, setEditingBenefit] = useState({id: null, name: '', category: ''})
useEffect(() => {
axios.get('/api/v1/benefits')
.then(response => {
setBenefits(response.data)
})
}, [])
const deleteBenefit = benefit => {
const id = benefit.id
axios.delete(`/api/v1/benefits/${id}`)
.then(response => {
setBenefits(benefits.filter(benefit => benefit.id !== id))
warning('Benefit Deleted')
})
.catch(error => {
warning('Benefit could not be deleted. Please try again')
})
}
return(
<div>
<Section>
<BenefitsTable
benefits={benefits}
deleteBenefit={deleteBenefit}
editBenefit={editBenefit}
/>
</Section>
</div>
)
}
My test is as follows:
it('deletes a benefit when the delete button is clicked', () => {
const { getByTestId } = render(<Benefits />)
moxios.wait(() => {
const request = moxios.requests.mostRecent()
request.respondWith({
status: 200,
response: benefits
}).then(() => {
done()
})
})
fireEvent.click(getByTestId('deleteButton1'))
moxios.wait(() => {
const request = moxios.requests.mostRecent()
request.respondWith({
status: 200,
}).then(() => {
expect(document.querySelectorAll('tbody > tr').length).toBe(1)
done()
})
})
})
The output is Unable to find an element by: [data-testid="deleteButton1"] and I get that it's because the axios request is async but I've tried wrapping the fireevent and subsequent axios request inside the then clause of the first axios request and the although the test passes, it passes with any value meaning it isn't being correctly processed.
Would waiting for the element to be present work?
it('deletes a benefit when the delete button is clicked', async () => {
const { getByTestId } = render(<Benefits />)
moxios.wait(() => {
const request = moxios.requests.mostRecent()
request.respondWith({
status: 200,
response: benefits
})
})
await waitForElement(getByTestId('deleteButton1'))
fireEvent.click(getByTestId('deleteButton1'))
moxios.wait(() => {
const request = moxios.requests.mostRecent()
request.respondWith({
status: 200,
}).then(() => {
expect(document.querySelectorAll('tbody > tr').length).toBe(1)
done()
})
})
})
I'm trying to use jest to test my componentDidMount method:
componentDidMount() {
agent.Gatherings.getAll().then((result) => {
this.setState({ gatherings: result }) //no code coverage
}).catch((err) => {
this.setState({ gatherings: [] }) //no code coverage
})
}
yet one of my other tests works fine:
it('test gathering List is rendered', () => {
wrapper.setState({ gatherings: [TestGathering] })
expect(wrapper.find('MyList').length).toEqual(1);
});
I want to have every line covered in my testing. How can I get the lines in my componentDidMount() to all be tested in jest?
UPDATE, I'm importing a file directly into the test file. The file I'm importing is called agent.js. The code that gets called in the function whose lines are missed are:
agent.js
export const requests = {
get: url => fetch(url).then(res => res.json()),
post: (url, body) =>
fetch(url, {
method: 'POST',
body: body,
headers: {
'Content-Type': 'application/json'
}
}).then(res => res.json()) //also this line lacks coverage
}
export const Gatherings = {
getAll: () =>
requests.get(API_ROOT + '/gatherings')
}
export default {
Gatherings
}
Issue
A line of code has to run while a test is running to be included in the Jest code coverage.
Details
The two lines without coverage are the callbacks for the Promise returned by agent.Gatherings.getAll.
Promise callbacks get added to the PromiseJobs queue and run after the current message completes and before the next message runs.
This is why those lines are not currently included in the code coverage...right now they don't run until after your synchronous test completes.
Solution
You just need to make sure those two lines run while a test is running.
Details
The ideal approach is to await the Promise directly in your test.
In this case the Promise is not easily accessible from within the test so a different approach is needed.
Workaround
If agent.Gatherings.getAll is mocked to resolve or reject immediately then the Promise callback will be queued in PromiseJobs by the time the component finishes rendering.
To let the Promise callback run use an async test function and call await Promise.resolve(); which essentially queues the rest of the test at the end of PromiseJobs and lets any pending jobs run first:
import * as React from 'react';
import { shallow } from 'enzyme';
import { Comp } from './code'; // <= import your component here
import * as agent from './agent';
describe('Component', () => {
let spy;
beforeEach(() => {
spy = jest.spyOn(agent.Gatherings, 'getAll');
})
afterEach(() => {
spy.mockRestore();
})
it('updates when agent.Gatherings.getAll() resolves', async () => { // use an async test function
const response = [ 'gathering 1', 'gathering 2', 'gathering 3' ];
spy.mockResolvedValue(response);
const wrapper = shallow(<Comp />); // render your component
await Promise.resolve(); // let the callback queued in PromiseJobs run
expect(wrapper.state()).toEqual({ gatherings: response }); // SUCCESS
});
it('handles when agent.Gatherings.getAll() rejects', async () => { // use an async test function
spy.mockRejectedValue(new Error());
const wrapper = shallow(<Comp />); // render your component
await Promise.resolve(); // let the callback queued in PromiseJobs run
expect(wrapper.state()).toEqual({ gatherings: [] }); // SUCCESS
});
});
You should now have code coverage on the Promise callbacks in componentDidMount.