I have an async method triggered by a click event where I make a call to an API and then process the response, like this:
async confirmName () {
const {name, description} = this.form;
const [data, error] = await Pipelines.createPipeline({name, description});
if (error) {
console.error(error);
this.serviceError = true;
return false;
}
this.idPipelineCreated = data.pipeline_id;
return true;
}
The test looks like this:
test("API success", async () => {
const ConfirmNameBtn = wrapper.find(".form__submit-name");
await ConfirmNameBtn.vm.$emit("click");
const pipelinesApi = new Pipelines();
jest.spyOn(pipelinesApi, "createPipeline").mockResolvedValue({pipeline_id: 100});
const {name, description} = wrapper.vm.form;
pipelinesApi.createPipeline().then(data => {
expect(wrapper.vm.pipelineNameServiceError).toBe(false);
wrapper.setData({
idPipelineCreated: data.pipeline_id
});
expect(wrapper.vm.idPipelineCreated).toBe(data.pipeline_id)
}).catch(() => {})
})
A basic class mock:
export default class Pipelines {
constructor () {}
createPipeline () {}
}
I'm testing a success API call and I mock the API call returning a resolved promised. The problem is the coverage only covers the first two lines of the method, not the part where I assign the response of the API call. Is this the correct approach?
Edit:
Screenshot of coverage report:
Don't mix up await and then/catch. Prefer using await unless you have very special cases (see this answer):
test("API success", async () => {
const ConfirmNameBtn = wrapper.find(".form__submit-name");
await ConfirmNameBtn.vm.$emit("click");
const pipelinesApi = new Pipelines();
jest.spyOn(pipelinesApi, "createPipeline").mockResolvedValue({pipeline_id: 100});
const {name, description} = wrapper.vm.form;
const data = await pipelinesApi.createPipeline();
expect(wrapper.vm.pipelineNameServiceError).toBe(false);
wrapper.setData({
idPipelineCreated: data.pipeline_id
});
expect(wrapper.vm.idPipelineCreated).toBe(data.pipeline_id)
expect(wrapper.vm.serviceError).toBe(false);
})
Related
I read some tips on how to mock your request/response in Express framework in the blog:
https://codewithhugo.com/express-request-response-mocking/. However, I have no clue how to mock the controller below.
export const healthCheck = async (req, res, next) => {
log("debug", "healthCheck controller called");
const healthcheck = {
uptime: process.uptime(),
message: "Server is running!",
now_timestamp: Date.now()
};
try {
res.send(healthcheck);
} catch (error) {
healthcheck.message = error;
res.status(503).send();
}
};
I am glad to share my efforts below. My suspicion is that I must mock class Date as well.
import {
healthCheck
} from "../healthcheck.js";
const mockRequest = () => {
const req = {}
req.body = jest.fn().mockReturnValue(req)
req.params = jest.fn().mockReturnValue(req)
return req
};
const mockResponse = () => {
const res = {}
res.get = jest.fn().mockReturnValue(res)
res.send = jest.fn().mockReturnValue(res)
res.status = jest.fn().mockReturnValue(res)
res.json = jest.fn().mockReturnValue(res)
return res
};
const mockNext = () => {
return jest.fn()
};
describe("healthcheck", () => {
afterEach(() => {
// restore the spy created with spyOn
jest.restoreAllMocks();
});
it("should call mocked log for invalid from scaler", async () => {
let req = mockRequest();
let res = mockResponse();
let next = mockNext();
await healthCheck(req, res, next);
expect(res.send).toHaveBeenCalledTimes(1)
expect(res.send.mock.calls.length).toBe(1);
});
});
I tried using redux to save token the one I get from api in react native ..its working now.
First one is for settoken and other one is for gettoken.
enter image description here
export const verifyOTP = (formValues, actions) => {
return async (dispatch) => {
dispatch(startSubmitting());
const url = `/validate-otp`;
var formdata = new FormData();
formdata.append("mobile", formValues.mobile);
formdata.append("otp", formValues.otp);
const response = await api.post(url, formdata);
dispatch({
type: "VERIFY_OTP",
payload: response,
});
dispatch(stopSubmitting());
await SecureStore.setItemAsync("userToken", response.data.access_token);
};
};
export const checkUser = () => {
return async (dispatch) => {
const token = await SecureStore.getItemAsync("userToken");
const url = `/me`;
const response = await api
.post(url, { token })
.then((res) => {
return res;
})
.catch((error) => {
return error.response;
});
dispatch({
type: "CHECK_USER",
payload: response,
});
};
};
The Problem
you are mixing two different implementations in checkUser to handle a promise which is clearly incorrect and leads to the issues.
The Solution
since your other parts of codes use the async/await so try to remove then/catch block from the response constant:
const checkUser = () => {
return async (dispatch) => {
const url = '/me';
try {
const token = await SecureStore.getItemAsycn("userToken);
const response = await api.post(url, {token})
dispatch({type: "CHECK_USER", payload: response})
} catch (error) {
// to proper action on failure case
}
}
}
Note 1: always use async/await in try/catch block. more on MDN documentation.
Optional
since you are trying to call two async actions (once for getting token and once for calling '/me' API), I encourage you to use two different try/catch blocks to handle the failure case for each async action separately. for example:
const checkUser = () => {
return async (dispatch) => {
let token = null;
try {
token = await SecureStore.getItemAsync("userToken");
} catch (err) {
// proper action in case of failure on getting the token from storage
}
// you may need to ignore API calls without the token, so:
try {
if(token){
const url = '/me';
const response = await api.post(url, {token});
dispatch({type: "CHECK_USER", payload: response});
}
} catch (err) {
// take proper action with the error response according to your applicaiton
}
}
}
No matter how I write, asynchronous problems occur.
test.js:
const auth = require('../methods/auth.js');
describe('test', () => {
test('test', async () => {
expect.assertions(1);
const data = await auth.signin();
return expect(data.success).toBeTruthy();
});
auth.js:
module.exports = {
async signin(data) {
try {
const res = await axios.post('/signin', data);
return res.data;
} catch (error) {
return error.response;
}
},
}
Each execution result is different.
You can test Promises as follows:
test('test awaiting it to resolve', () => {
// Don't await. Note the return; test callback doesn't need to be async
// What I do in my tests as its more readable and the intention is clear
return expect(auth.signin()).resolves.toEqual({success: true});
});
test('test the promises way', () => {
// Not better than above; traditional way; note 'return'
return auth.signin().then(data => { expect(data.success).toBeTruthy() });
});
Detailed notes from jest: https://jestjs.io/docs/asynchronous#promises
I am building a page to fetch data from API when loaded, but encounter waring an effect function must not return anything besides a function which is used for clean-up when trying to reuse the function for fetching data
const dispatch = useDispatch();
useEffect(() => {
// this way does not work as I expected, my page does not show data I fetched
const getData = async () => {
const result = await dispatch(actions.getList());
setState(result);
};
getData();
},[isFirstLoaded]);
But I get the warning when trying below
const dispatch = useDispatch();
const getData = async () => {
const result = await dispatch(actions.getList());
setState(result);
};
useEffect(async() => {
// this way gives me the data but with a warning
await getData();
},[isFirstLoaded]);
How should I reuse the getData function? I did not update the state if I am not using the async and await here. When I use async and await here, I get the warning. Thanks.
overall, you are heading in the right direction. For fetching data, you'd wanna use use Effect and pass [] as a second argument to make sure it fires only on initial mount.
I believe you could benefit from decoupling fetching function and making it more generic, as such:
const fetchJson = async (url) => {
const response = await fetch(url);
return response.json();
};
const Fetch = () => {
const [data, setData] = useState(null);
useEffect(() => {
fetchJson("https://api.coindesk.com/v1/bpi/currentprice.json")
.then(({ disclaimer }) => setData(disclaimer));
}, []);
return <Text>{data}</Text>;
};
I have an api.js file that contains my api call. It looks like this:
// api.js
// reusable fetch call
export const makeFetch = async (url, options) => {
try {
const response = await fetch(url, options);
if (!response.ok) {
throw new Error(`${response.status}`);
}
return await response.json();
} catch (error) {
throw new Error(`Network request failed. (error: ${error.message})`);
}
};
// actual fetch call
export const getCards = async () => {
const url = 'http://localhost:3001/api/v1/cards';
return await makeFetch(url);
};
Then I have an api.test.js file that looks like:
//api.test.js
import { makeFetch, getCards } from './api';
describe('makeFetch', () => {
// three successful tests
});
// this is where I have issues
describe('getCards', () => {
it('calls makeFetch', async () => {
await getCards();
expect(makeFetch).toHaveBeenCalledTimes(1);
});
});
this is the error I get:
FAIL src\api\api.test.js
● getCards › calls makeFetch
ReferenceError: makeFetch is not defined
at Object.it.only (src/api/api.test.js:51:15)
at new Promise (<anonymous>)
at Promise.resolve.then.el (node_modules/p-map/index.js:46:16)
at <anonymous>
at process._tickCallback (internal/process/next_tick.js:182:7)
Does anyone know how to make this pass with making my previous tests fail? Thank you for your time.
You need to create a mock of makeFetch. You can do that using spyOn and mockReturnValue.
You will need to move makeFetch into its own file (lib.js) so that you can mock and replace it in your test:
// ---- lib.js ----
// reusable fetch call
export const makeFetch = async (url, options) => {
try {
const response = await fetch(url, options);
if (!response.ok) {
throw new Error(`${response.status}`);
}
return await response.json();
} catch (error) {
throw new Error(`Network request failed. (error: ${error.message})`);
}
};
// ---- api.js ----
import { makeFetch } from './lib';
// actual fetch call
export const getCards = async () => {
const url = 'http://localhost:3001/api/v1/cards';
return await makeFetch(url);
};
// ---- api.test.js ----
import * as lib from './lib';
import { getCards } from './api';
describe('makeFetch', () => {
// three successful tests
});
describe('getCards', () => {
it('calls makeFetch', async () => {
const simulatedResponse = { data: 'simulated response' };
// mock makeFetch
const mock = jest.spyOn(lib, 'makeFetch');
mock.mockReturnValue(Promise.resolve(simulatedResponse));
const result = await getCards();
expect(result).toEqual(simulatedResponse);
expect(mock).toHaveBeenCalledTimes(1);
expect(mock).toHaveBeenCalledWith('http://localhost:3001/api/v1/cards');
// restore makeFetch
mock.mockRestore();
});
});