Hi guys I'm having troubles to test an async function with a fetch to a server inside. I'm using mocha with chai-as-promised. The test that is failing is: 'return proper title' I guess I would have to mock the fetch call or something, or maybe the problem is that I'm calling an async function, and as I'm executing a unit test and I don't resolve the promise. I'm not pretty sure how to achieve that. Could you help me?
The function to test is:
import React from 'react'
import Technologies from './Technologies'
import fetch from '../../core/fetch'
let title= 'Technologies'
export default {
path: '/technologies',
async action () {
const resp = await fetch('/graphql', {
method: 'post',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
query: '{technologies{name,icon,url}}',
}),
credentials: 'include',
})
let { data, } = await resp.json()
if (!data || !data.technologies) throw new Error('Failed to load technologies.')
return {
title:title,
component: <Technologies title={title} technologies={data.technologies} />,
}
},
}
And my tests:
describe('Route', () => {
it('has right path', () => {
expect(Route.path === '/technologies').to.be.true
})
it('return proper title', () => {
const title = 'Technologies'
expect(Route.action().title === title).to.be.true
})
})
try with:
describe('Route', () => {
it('has right path', () => {
return expect(Route.path === '/technologies').to.be.true
})
it('return proper title', () => {
const title = 'Technologies'
return expect(Route.action().title === title).to.be.true
})
})
The first strategy suggested in the mocha documentation is using the ‘done’ callback. This is an extra argument to the callback in the it . You call it after the last assertion in your test.
for example, for your test you have forget to return the function in expect :
describe('Route', () => {
it('has right path', (done) => {
return expect(Route.path === '/technologies').to.be.true
done();
})
it('return proper title', (done) => {
const title = 'Technologies'
return expect(Route.action().title === title).to.be.true
done();
})
})
Related
I have an API (let's call it getToken) to generate a token in its response body. I then call that token and store in the header of another API (let's call it returnBody). It makes sense to use localStorage for the getToken API as this token is re-usable for multiple API's. However, I am having doubts using localStorage if I need to return / display the response body of the succeeding API's such as returnBody. Inside the API's function/command, it logs the response body. However, when I call it via the test file, it generates null.
Sample code below:
commands.js:
Cypress.Commands.add('getToken', () => { //API to generate token
cy.request({
method: 'POST',
url: 'https://someAPItoGenerateToken',
form: true, //sets to application/x-www-form-urlencoded
body: {
grant_type: 'credentials',
scope: 'all-apis'
},
auth: {
username: Cypress.env('api_username'),
password: Cypress.env('api_password')
}
})
.its('body')
.then(bearerToken => {
cy.setLocalStorage('bearerToken', JSON.stringify(bearerToken))
cy.log('Token generated: ' + bearerToken.token)
}
)
})
Cypress.Commands.add('returnBody', (url, token) => { //API to return some values
return cy.request({
method: 'GET',
url: url,
auth: {
bearer: token
}
})
.then((response) => {
// Stringify JSON the body.
let body = JSON.stringify(response.body)
cy.log(body)
})
})
test file:
describe('Return value of 2nd API', ()=> {
before(() => {
cy.getToken() //Run this once to generate token for the entire test suite
cy.saveLocalStorage()
})
beforeEach(() => {
cy.restoreLocalStorage()
})
it('Return value of 2nd API', () => {
cy.getLocalStorage('bearerToken').should('exist')
cy.getLocalStorage('bearerToken').then(bearerToken => {
const tokenJSON = JSON.parse(bearerToken)
const url = 'https://someAPItoReturnJSONbody'
cy.returnBody(url, tokenJSON.token).then((returned_value) => {
cy.log(returned_value)
})
})
})
})
body from the returnBody command returns the JSON response. However, returned_value from the test file displays null.
As commented in this issue: "You cannot return a 3rd party promise from a custom command because this breaks the chaining between cypress commands. This is because the .then methods are not the same."
So, simply return the request body as:
Cypress.Commands.add('returnBody', (url, token) => {
return cy.request({ /* options */ })
.its("body");
});
Then, in your test you can do:
it("should return foo value", () => {
cy.returnBody(url, token).then(returned_value => {
cy.log(returned_value);
expect(returned_value).to.deep.equal("foo-value");
})
})
You may need to return response body from your returnBody task:
Cypress.Commands.add('returnBody', (url, token) => {
return cy.request({ /* options */ })
.then(response => {
let body = JSON.stringify(response.body);
Cypress.log(body);
return body; // Add this line
});
});
An alternative would be to store the token on cypress side using fixtures.
fixture.json:
{"bearerToken":""
.
.
.}
commands.js:
cy.fixture('testData.json').as('testData')
.
.
.then(bearerToken => {
this.testData.bearerToken = JSON.stringify(bearerToken)
cy.log('Token generated: ' + bearerToken.token)
}
)
test.js
describe('Return value of 2nd API', ()=> {
before(() => {
cy.getToken() //Run this once to generate token for the entire test suite
})
it('Return value of 2nd API', () => {
cy.fixture('testData.json').as('testData')
.then(testData => {
const tokenJSON = JSON.parse(testData.bearerToken)
const url = 'https://someAPItoReturnJSONbody'
cy.returnBody(url, tokenJSON.token).then((returned_value) => {
cy.log(returned_value)
})
})
})
})
I have a function, as below, that performs 2 fetch calls within the same function
getNames() {
var qs = require("qs");
fetch(<URL>,
{
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8'
},
body: qs.stringify({
firstName: this.state.firstName,
lastName: this.state.lastName
})
})
.then(response => response.json()).then((data) => {
console.log(data)
});
var url = new URL(<someURL>)
fetch(<someURL>).then(response => response.json()).then((data) => {
...do something...
}
})
.catch(error => {
alert("no response");
console.log(error);
});
}
I am testing this using Jest and Enzyme on React. The above belongs to the GetName component. Below is my test case:
describe('getName', () => {
const wrapper = shallow(<GetName />).instance();
beforeEach(() => {
global.fetch.resetMocks();
});
it('positive flow', () => {
global.fetch.mockResolvedValue(
new Response(JSON.stringify({data: "mockData"}))
);
const state = {
firstName: "don",
lastName: "Lee"
};
wrapper.setState(state);
const actualValue = wrapper.getNames();
expect(actualValue).toBeUndefined();
});
});
Once I do this, I get an error that TypeError: body used already for: undefined
I understand that the fetch here is being used for the POST call, but how do I make sure that I can mock both the fetch calls within the function?
I have also tried fetch.mockResponse and fetch.mockResponses and also fetch.mockResponseOnce. None of them seem to help me mock them more than once and I get this error with all functions mentioned.
Is there any other way to mock both the fetch calls?
I recently started testing my React app. However, I stumbled when dealing with submitting forms. My test covers most of the lines but misses out on actual part of submit form method.
LoginForm.js - submit form
const userLoginData = {
userId : this.state.userId,
password : this.state.password,
userType : this.state.userType
};
axios({
data : JSON.stringify(userLoginData),
type : 'post',
url : Constant.BASE_URL_SERVER+'/rest/login',
headers : {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
cache : false
})
.then(function (response) {
//alert("Form Submitted.");
this.setState({isLoggedIn : true});
this.setState({loginResponse : "Login Success!"});
if(this.state.userType === 'Customer'){
...
login_form-test.js
describe('testing form submission onSubmit', () => {
const testData = {
userId: '00000000',
password: 'SamplePassword0',
userType: 'Customer',
validForm: true,
}
it('should submit form onSubmit()', () => {
const mountedComponentHandle = mount(<LoginForm {...testData}/>);
const onSubmitForm = sinon.spy(
mountedComponentHandle.instance(),
'handleSubmitForm'
);
mountedComponentHandle.update();
const formHandle = mountedComponentHandle.find('form');
expect(formHandle.length).toBe(1);
formHandle.simulate('submit');
expect(onSubmitForm.called).toBe(true);
});
});
Please suggest on how to test .then() and .catch() of axios.
Thanks.
Key here is to make your code "testable". Separating responsibility helps to make your code more testable, readable and easy to maintain. In your case logic to post data over an API lies in some service which will handle api requests for your app, and you can test it separately.
Coming back to your question, I am providing you one of the possible solutions for testing async calls in your case:
// apiGateway.js
const postData = (url, data) => (
axios({
data: JSON.stringify(data),
type: 'post',
url: BASE_URL_SERVER + url,
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
cache: false
})
);
Again you can test above code separately.
// myAppApi.js
const postLoginForm = (data, callback, errorCallback) => {
return postData('/rest/login', data)
.then((response) => callback(response.data))
.catch((error) => errorCallback(error))
};
// myAppApi.test.js
// import * as myAppApi from '../myAppApi'
it('should call callback when response is successful', async () => {
const mockResponse = {};
const mockRequestData = {};
const mockSuccessCallback = jest.fn();
const mockErrorCallback = jest.fn();
spyOn(myAppApi, 'postLoginForm').and.returnValue(Promise.resolve(mockResponse));
await myAppApi.postLoginForm(mockRequestData, mockSuccessCallback, mockErrorCallback);
expect(mockSuccessCallback).toHaveBeenCalled();
});
it('should call error callback when response is failed', async () => {
const mockRequestData = {};
const mockSuccessCallback = jest.fn();
const mockErrorCallback = jest.fn();
spyOn(myAppApi, 'postLoginForm').and.returnValue(Promise.reject());
await myAppApi.postLoginForm(mockRequestData, mockSuccessCallback, mockErrorCallback);
expect(mockErrorCallback).toHaveBeenCalled();
});
In above tests you can use different mocking methods or libraries.
And finally your component will look something like this
// LoginForm.js
class LoginForm extends React.Component {
onSuccessfulLogin(responseData) {
//.. success logic here
}
onFailedLogin(error) {
//.. error logic here
}
onSubmitForm(event) {
postLoginForm(this.state.data, this.onSuccessfulLogin, this.onFailedLogin)
}
}
As you can see separating out logic helps in testing. Further it will save you from ending up with component with tons of code in it. You can test your component for its state and presentation.
Hope this answers your question!
I have a common function which uses FETCH to get data from external web service. this function will be invoked and parsed in multiple screens under componentDidMount(). instead of repeating the same code at multiple places, I put below under a common class, but unfortunately, data is not returned to those screens.
Common Function
export function convertValue(fromVal, toVal) {
var requestObj = {};
let apiEndpoint = '<target endpoint>'
return fetch(apiEndpoint, {
method: 'GET',
headers: {
'Content-Type': 'application/json'
},
})
.then((response) => response.json())
.then((responseJson) => {
return responseJson;
})
.catch((error) => {
console.log('Error: ', error);
});}
Sample call below, and no pop-up when screen loaded.
componentDidMount () {
AsyncStorage.getItem('user_default').then((value) => {
this.setState({userDefault: value});
}).then((value) => {
var sample = convertValue('A', 'B');
Alert.alert(
'Success',
JSON.stringify(sample),
[
{text: 'OK',
onPress: () => {
console.log('.');
}}
]
)
});}
componentDidMount () {
AsyncStorage.getItem('user_default').then((value) => {
this.setState({userDefault: value});
convertValue('A', 'B').then((json)=>{
alert(json)
})
})}
This might work for you. The problem was improper chaining of asynchronous calls.
Nick is right, found the right way -
convertValue('A', 'B')
.then((responseJson) => {
this.setState({returnedValue: responseJson.convertedValue});
});
I'm trying to test this simple api module:
import fetch from 'isomorphic-fetch';
export const getJson = (endpoint: string) => {
const options = { credentials: 'include', method: 'GET' };
return fetch(endpoint, options)
.then(response => response.json()
.then(json => {
if (response.ok) return json;
return Promise.reject(json.errors);
})
)
.catch(error => {
if (error.constructor === Array) return error;
return [error.message];
});
};
With this test, where I'm mocking fetch:
import { getJson } from '../api';
const mockResponse = (status, statusText, response) => {
return new window.Response(response, {
status: status,
statusText: statusText,
headers: {
'Content-type': 'application/json'
}
});
};
describe('api middleware', () => {
describe('getJson', () => {
it('should return the response on success', () => {
const expected = { data: ['data'], meta: {} };
const body = JSON.stringify(expected);
window.fetch = jest.fn().mockImplementation(() =>
Promise.resolve(mockResponse(200, null, body)));
return getJson('http://endpoint').then(actual => expect(actual).toEqual(expected));
});
});
});
But the test fails with:
Expected value to equal:
{"data": ["data"], "meta": {}}
Received:
["Unexpected end of JSON input"]
Difference:
Comparing two different types of values:
Expected: object
Received: array
I've not been able to figure out why this isn't working. Why am I receiving the "Unexpected end of JSON input" error? And how do I successfully mock fetch locally in a test? In this medium post it's done in basically the same way..
So apparently the test was still using the global fetch library, and not my patched version. The solution was to:
Remove the 'isomorphic-fetch' mock (in __mocks__ at the root of the project).
Import 'isomorphic-fetch' once at the root of my project with import 'isomorphic-fetch;
Remove the 'isomorphic-fetch' import at the top of my api module (since it's already imported at the entrypoint
Update the test to:
test:
// to make the Response constructor available
import 'isomorphic-fetch';
import { getJson } from '../api';
describe('api middleware', () => {
describe('getJson', () => {
beforeEach(() => {
window.fetch = jest.genMockFunction();
});
it('should return the response on success', () => {
const expected = { data: ['data'], meta: {} };
const body = JSON.stringify(expected);
const init = { status: 200, statusText: 'OK' };
window.fetch.mockReturnValueOnce(Promise.resolve(new Response(body, init)));
return getJson('http://endpoint').then(actual => expect(actual).toEqual(expected));
});
});
});
Most probably because your getJson function does not use the global (window) fetch.
The way I would suggest doing it is to use Dependency Injection (DI); make getJson retrieve the the "http request" library/function (in your case fetch) and in your tests, create a mock function which is injected. The mock function will return the data that you want as part of testing.