We are working on a react + typescript project & trying to implement unit testing using cypress.
I have a component called GettingStartedForm where two APIs are called on componentDidMount
Create API
Get API - is called once the creation is successful.
We are trying to mock both API calls but the Get API call is not getting mocked.
Here is the component,
componentDidMount() {
this.createMethod();
}
/* Create Method */
createMethod() {
const contentType = 'application/json';
const pathAPICreate = '/v1/create/';
postData(pathAPICreate, contentType, '')
.then((response:any)=> {
this.getData();
}).catch((error:any)=>{
...
});
}
/* Get step data */
async getData() {
const contentType = 'application/json';
const pathData = '/v1/data/';
try {
const dataResponse = await getCreatedData(
pathData,
contentType,
);
...
}
Here is my test,
import GettingStartedForm from '../GettingStartedForm';
import React from 'react';
import {mount} from '#cypress/react';
describe('Onboarding getting started form - step 1', () => {
beforeEach(() => {
cy.intercept('POST', '**/v1/create', {data: 'success'}).as('createConsultant');
cy.intercept('GET', '**/v1/data/', {fixture: 'gettingStartedStepResponse.json'}).as('stepData');
});
it('Should render all elements', () => {
mount(<GettingStartedForm
nextFormHandler={(e, formStatus) => false}
previousFormHandler={(e, formStatus) => false}
></GettingStartedForm>);
...
});
});
and console message here
Is there something we are missing here? Any help is highly appreciated.
Related
I am struggling with figuring out how to correctly mock this Axios post in my React test. It seems like the documentation and tutorials out there are all over the place in terms of how they do it, no strong consensus or best practice.
So here is my Test:
BatchList.test.js
import React from 'react';
import { setupWorker, rest } from 'msw';
import {
render,
screen,
within,
fireEvent,
waitFor,
} from '#testing-library/react';
import '#testing-library/jest-dom';
import { Provider as AlertProvider } from 'react-alert';
import AlertMUITemplate from 'react-alert-template-mui';
import BatchList from './BatchList';
import mockedAxios from 'axios';
// ... other tests that succeed here ...
// only test that fails here
test('clicking yes and passing back only 2 valid batch numbers should show 2 valid, 2 invalid batch numbers in list', async () => {
// renderBatchListWithNumbers();
const batchList = render(
<AlertProvider template={AlertMUITemplate}>
<BatchList
formulaID={''}
orderID={''}
itemID={''}
formulaResults={[]}
batchNumbers={[
{ BATCH_ID: '987654', ID: '78' },
{ BATCH_ID: '261010', ID: '79' },
{ BATCH_ID: '301967', ID: '80' },
{ BATCH_ID: '445566', ID: '81' },
]}
setBatchNumbers={mockedEmptyFn}
batchNumber={'5'}
setBatchNumber={mockedEmptyFn}
/>
</AlertProvider>
);
const completeButton = batchList.getByText('Complete');
fireEvent.click(completeButton);
const yesButton = batchList.getByText('Yes');
expect(yesButton).toBeInTheDocument();
fireEvent.click(yesButton);
// now we need to figure out mocking the API calls!!!
const data = {
msg: 'fail',
invalidBatchNumbers: ['987654', '445566'],
};
mockedAxios.post.mockResolvedValueOnce({
data: data,
});
await waitFor(() => {
expect(batchList.getByText('987654')).toHaveClass('invalid');
expect(batchList.getByText('261010')).toHaveClass('valid');
});
});
And here is my axios.js inside my __mocks__ folder:
export default {
post: jest.fn().mockResolvedValue(),
};
So the error I am getting in my test is this: thrown: "Unable to complete all batches - TypeError: (0 , _axios.default) is not a function"
And that error message string is coming from my client side API call here:
export const completeAllBatches = async (
orderID,
itemID,
batchNumbers
) => {
const completeAllBatchesURL =
serverURL + `:${port}/completeAllBatches`;
try {
return await axios({
method: 'post',
url: completeAllBatchesURL,
timeout: shortTimeout,
data: {
orderID,
itemID,
batchNumbers,
},
})
.then((res) => {
return res;
})
.catch((e) => {
return Promise.reject(
'1: Unable to complete all batches - ' + e
);
});
} catch (e) {
return Promise.reject('2: Unable to complete all batches - ' + e);
}
};
I solved a similar problem, perhaps this is useful?
I was following this tutorial.
Why this break?
I think the problem is that typescript doesn't know how to handle some of the complexities of an axios.post.
Instead of importing default as mocked, I imported as normal
import axios from 'axios'
jest.mock('axios');
Then I am a bit more explicit with the mock
const mockedAxios = axios as jest.Mocked<typeof axios>;
let payload:object = {}
const mockedPost = mockedAxios.post.mockReturnValueOnce(payload);
//I highly recommend separating out api client like in the tutorial, then call that api function...
const data = await postBatch();
expect(axios.post).toHaveBeenCalled();
LMK if this works for you, I'm still playing with my React testing patterns :)
I'm using JEST testing framework to write test cases for my React JS application. I'm using our internal axios wrapper to make service call. I would like to mock that wrapper service using JEST. Can someone help me on this ?
import Client from 'service-library/dist/client';
import urls from './urls';
import { NODE_ENV, API_VERSION } from '../screens/constants';
const versionHeader = 'X-API-VERSION';
class ViewServiceClass extends Client {
getFiltersList(params) {
const config = {
method: urls.View.getFilters.requestType,
url: urls.View.getFilters.path(),
params,
headers: { [versionHeader]: API_VERSION },
};
return this.client.request(config);
}
const options = { environment: NODE_ENV };
const ViewService = new ViewServiceClass(options);
export default ViewService;
Above is the Service Implementation to make API call. Which I'm leveraging that axios implementation from our internal library.
getFiltersData = () => {
const params = {
filters: 'x,y,z',
};
let {
abc,
def,
ghi
} = this.state;
trackPromise(
ViewService.getFiltersList(params)
.then((result) => {
if (result.status === 200 && result.data) {
const filtersJson = result.data;
.catch(() =>
this.setState({
alertMessage: 'No Filters Data Found. Please try after some time',
severity: 'error',
showAlert: true,
})
)
);
};
I'm using the ViewService to get the response, and I would like to mock this service. Can someone help me on this ?
You would need to spy your getFiltersList method from ViewServiceClass class.
Then mocking some response data (a Promise), something like:
import ViewService from '..';
const mockedData = {
status: 'ok',
data: ['some-data']
};
const mockedFn = jest.fn(() => Promise.resolve(mockedData));
let getFiltersListSpy;
// spy the method and set the mocked data before all tests execution
beforeAll(() => {
getFiltersListSpy = jest.spyOn(ViewService, 'getFiltersList');
getFiltersListSpy.mockReturnValue(mockedFn);
});
// clear the mock the method after all tests execution
afterAll(() => {
getFiltersListSpy.mockClear();
});
// call your method, should be returning same content as `mockedData` const
test('init', async () => {
const response = await ViewService.getFiltersList();
expect(response).toEqual(mockedData);
});
P.D: You can pass params to the method, but you will need to configure as well the mockedData as you wish.
I'm running tests via node with Jest, on a Next/React project.
I'm also using cross-fetch as well.
When I try to mock cross-fetch for my component
import crossFetch from 'cross-fetch'
jest.mock('cross-fetch')
crossFetch.mockResolvedValue({
status: 200,
json: () => {{
user : testUser
}},
})
render(<UserProfile />)
The API request in the getServerSideProps
always returns 500
export async function getServerSideProps({ query: { userId } }) {
let user = null
let code = 200
try {
let response = await fetch(`https://example.com/users/${userId}`, { method: 'GET' })
let statusCode = response.status
let data = await response.json()
if (statusCode !== 200) {
code = statusCode
} else {
user = data.user
}
} catch (e) {
console.log(e.message)
}
return {
props: {
user,
code,
},
}
}
I have a feel it has something to do with the tests being initiating from Node and testing-library library is simulating a browser, that the actual lib making the request is not being mocked for the correct execution environment (browser in my case). But I'm not entirely sure.
Thanks in advance
Perhaps it's not working because of the default export being a function. Try this:
//test.js
import crossFetch from 'cross-fetch';
jest.mock('cross-fetch', () => {
//Mock the default export
return {
__esModule: true,
default: jest.fn()
};
});
test('should do a mock fetch', () => {
crossFetch.mockResolvedValue({
status: 200,
json: () => {{
user: testUser
}},
});
expect(crossFetch().status).toEqual(200);
});
I make use of fetch-mock (https://www.wheresrhys.co.uk/fetch-mock/) when testing fetch calls. You may need to provide a polyfill for fetch to be understood in the context of Jest tests, and this can be done with an import "cross-fetch/polyfill"; in the file where fetch is being used.
Note that the Create React App generated environment handles the necessary polyfill imports, but I'm not sure if Next.js has something similar.
I have an issue where I need to be mocking a class Api that is called within my redux actions, this class calls axios get, post etc... which need to be mocked. I have been following this tutorial explaining how to mock axios and this tutorial about how to mock a class but neither approaches appear to be working.
Now for some code... here's an example of the type of action I need to test.
export const getAlldata = (id: string) => {
return (dispatch: any) => {
dispatch(beginAjaxRequest(id, types.BEGIN_GET_DATA_AJAX));
return Api.get("/data/data").then((response: any) => {
dispatch(getDataSuccess(response.data, id))
}).catch((error) => {
dispatch(handleAjaxError(id, new Alert({ id: id, title: "Error getting data", message: error.toString(), timestamp: Date.now(), error: true })));
});
}
}
and the parts of Api this calls.
import axios from 'axios';
class Api {
static get(path: string) {
return axios({
method: 'get',
url: (global as any).apiDomain + path,
headers: {
Authorization: "Bearer " + (global as any).authentication.getToken(),
"Content-Type": "application/json"
}
});
}
}
export default Api;
Which I have tried to mock in src/mocks/Api (two underscores following and preceding mocks)
import * as Promise from 'bluebird';
import { getTestData } from '../models/_tests/TestData';
class Api {
static get(path: string) {
switch (path) {
case "/data/data":
return Promise.resolve({
data: getTestData(3)
});
default:
return {};
}
}
}
export default Api;
and setup in my setupTests.
import * as Enzyme from 'enzyme';
import Api from './__mocks__/Api';
const Adapter = require("enzyme-adapter-react-16");
(global as any).Api = Api;
Enzyme.configure({ adapter: new Adapter() });
and called in my actual test...
describe('thunk actions', () => {
var middleware = [thunk];
var mockStore = configureMockStore(middleware);
afterAll(() => {
cleanAll();
});
test('getAllData gets all data', (done: any) => {
var store = mockStore({});
jest.mock('../../api/Api'); // path to real Api
var id = generateGuid();
store.dispatch<any>((getAllData(id))).then(() => {
done();
});
});
});
So obviously this doesn't actually test anything, I'm just trying to get this working but I keep getting errors within the real Api instead of the mock. I have also tried mocking axios but I get the same error (can't getToken of undefined) so this doesn't seem to be replacing either axios or Api, can anyone see where I'm going wrong?
You know you're screwed when you post a question to stackoverflow and get 0 answers and 0 responses in over a week... Not ideal but I've found a workaround to override the Api class in my thunk actions, instead of importing the Api class into all my action files and calling it directly, I now only import it into the root of my project (App.tsx) and make it global as below (stripped down to it's bare minimum).
import * as React from 'react';
import Api from './api/Api';
export interface State {
}
export interface Props {
}
export class App extends React.Component<Props, State> {
state = {
};
componentWillMount = () => {
(global as any).Api = Api;
};
public render() {
return (
<div>
</div>
);
}
}
export default App;
...and then call Api on my thunk actions as below
export const getAlldata = (id: string) => {
return (dispatch: any) => {
dispatch(beginAjaxRequest(id, types.BEGIN_GET_DATA_AJAX));
return (global as any).Api.get("/data/data").then((response: any) => {
dispatch(getDataSuccess(response.data, id))
}).catch((error) => {
dispatch(handleAjaxError(id, new Alert({ id: id, title: "Error getting data", message: error.toString(), timestamp: Date.now(), error: true })));
});
}
}
Then simply override this is setupTests.ts
import * as Enzyme from 'enzyme';
import Api from './__mocks__/Api';
const Adapter = require("enzyme-adapter-react-16");
(global as any).Api = Api;
Enzyme.configure({ adapter: new Adapter() });
...and then there's no need for jest mocks, simply call the actions in your tests and test.
This method would also work outside of Node by replacing global with window. This does the job but is not ideal as I prefer not to use the global namespace, so if anyone knows a better way post away.
I guys I created a service in React and I need to test this part of the service, I'm using axios and Jest to do this.
I have the next code in React :
import axios from 'axios';
import Endpoints from './endpoints';
const baseUrl = Endpoints.getBackendEndpoint();
export const validateName = (nameObject, callback) => {
axios.post(`${baseUrl}/validateName`, {...nameObject})
.then(response =>{
response.data
})
.then(data => callback(data));
};
I don't need return the promise because all the work is doing by the callback() function.
This is the code that I have in Jest:
mport moxios from 'moxios';
import * as service from '../service';
import mockResponses from './service.test.json';
import Endpoints from '../endpoints';
const validateObjName = {
Id: 1,
Name: 'Bob',
}
beforeEach(() => {
const baseUrl = Endpoints.getBackendEndpoint();
moxios.stubRequest(
`${baseUrl}/validateName`,
{ ...validateObjName },
{
status: 200,
response: mockResponses.validateForm,
}
);
});
afterEach(() => {
moxios.uninstall();
});
it('validateName()', () => {
service.validateName(validateObjName, jest.fn());
});
It works, but still need to increase the Branch coverage.
Thanks for you help guys :D
To get code coverage the code has to run while a test is running so you will want to return the Promise so you can await it in your test so the then callbacks run during your test.
Also, you can simplify validateName to this:
import axios from 'axios';
import Endpoints from './endpoints';
const baseUrl = Endpoints.getBackendEndpoint();
export const validateName = (nameObject, callback) => {
return axios.post(`${baseUrl}/validateName`, { ...nameObject })
.then(response => callback(response.data));
};
In your test you need to install moxios in your beforeEach and pass the mock response as the second parameter to moxios.stubRequest.
Then use an async test function and await the Promise returned by validateName:
import moxios from 'moxios';
import * as service from '../service';
import mockResponses from './service.test.json';
import Endpoints from '../endpoints';
const validateObjName = {
Id: 1,
Name: 'Bob',
}
beforeEach(() => {
moxios.install(); // install moxios
const baseUrl = Endpoints.getBackendEndpoint();
moxios.stubRequest(
`${baseUrl}/validateName`,
{
status: 200,
response: mockResponses.validateForm
}
); // response is the second argument
});
afterEach(() => {
moxios.uninstall();
});
it('validateName()', () => {
service.validateName(validateObjName, jest.fn());
});
it('validateName()', async () => { // use an async test function
const spy = jest.fn();
await service.validateName(validateObjName, spy); // await the Promise
expect(spy).toHaveBeenCalledWith(mockResponses.validateForm); // Success!
});
That should give you a working test and 100% code coverage.