I'm testing apiMiddleware that calls its helper function callApi. To prevent the call to actual callApi which will issue the API call, I mocked the function. However, it still gets called.
apiMiddleware.js
import axios from 'axios';
export const CALL_API = 'Call API';
export const callApi = (...arg) => {
return axios(...arg)
.then( /*handle success*/ )
.catch( /*handle error*/ );
};
export default store => next => action => {
// determine whether to execute this middleware
const callAPI = action[CALL_API];
if (typeof callAPI === 'undefined') {
return next(action)
}
return callAPI(...callAPI)
.then( /*handle success*/ )
.catch( /*handle error*/ );
}
apiMiddleware.spec.js
import * as apiMiddleware from './apiMiddleware';
const { CALL_API, default: middleware, callApi } = apiMiddleware;
describe('Api Middleware', () => {
const store = {getState: jest.fn()};
const next = jest.fn();
let action;
beforeEach(() => {
// clear the result of the previous calls
next.mockClear();
// action that trigger apiMiddleware
action = {
[CALL_API]: {
// list of properties that change from test to test
}
};
});
it('calls mocked version of `callApi', () => {
const callApi = jest.spyOn(apiMiddleware, 'callApi').mockReturnValue(Promise.resolve());
// error point: middleware() calls the actual `callApi()`
middleware(store)(next)(action);
// assertion
});
});
Please ignore the action's properties and argument of callApi function. I don't think they are the concern of the point I'm trying to make.
Tell me if you need further elaboration.
The jest mocking only works on imported functions. In your apiMiddleware.js the default function is calling callApi variable, not the "exported" callApi function. To make the mock work, move callApi into its own module, and import it in apiMiddleware.js
Good question!
I solved my issues converting my code to a Class, example:
// Implementation
export class Location {
getLocation() {
const environment = this.getEnvironmentVariable();
return environment === "1" ? "USA" : "GLOBAL";
}
getEnvironmentVariable() {
return process.env.REACT_APP_LOCATION;
}
}
// Test
import { Location } from "./config";
test('location', () => {
const config = new Location();
jest.spyOn(config, "getEnvironmentVariable").mockReturnValue("1")
const location = config.getLocation();
expect(location).toBe("USA");
});
Related
In the setup below, if I run the test as is, myFunc is not mocked when I debug into handler.
However, if instead I add this. in front of the myFunc call in handler, then the function is mocked and everything works as expected.
Can someone please explain why this is? I'm new to mocking and can't see it.
I know what this does, but why won't jest mock without it since I told it to mock that function in the module?
index.js
const aws = require('aws-sdk')
exports.handler = async function (event, context) {
let s;
switch (event.func) {
case "myFunc":
console.log('Executing myFunc');
//making the call: s = await this.myFunc.apply(null, [event.params]) will make the mock work.
s = await myFunc.apply(null, [event.params])
console.log(s);
return s;
/*cases...*/
default:
// default behaviour
}
async myFunc({p1, p2}){
/* do something */
return x
}
exports.myFunc = myFunc
}
index.spec.js
jest.mock('./index.js', () => {
const allAutoMocked = jest.createMockFromModule('./index.js')
const actual = jest.requireActual('./index.js')
return {
__esModules: true,
...allAutoMocked,
myFunc : jest.fn().mockImplementation(() => ({ mockedValue: 'test' })),
handler: actual.handler
}
})
let index = require("./index.js")
describe('Test myFunc', () => {
test('If myFunc function was called', async () => {
var event = { func: 'myFunc', params: { p1: xx, p2: false } };
const context = {};
const logMock = jest.fn((...args) => console.log(...args));
const data = await handler(event, context);
})
})
I'm new to react, here I have two same codes, one is with classes that work, and another is converted from that same class into hooks.
in hooks version, my 'then' is giving an error
Property 'then' does not exist on type '(dispatch: any) =>
Promise'.ts(2339)
have I made some mistake with conversion?
why it is not giving the same error in class while both are the same?
also console.log("Fetched model", realGraph.model); should give an object but it is giving undefined(in-class version it works), but if I put this console outside of loadGraph function then it gives an object, why it's not giving an object inside loadGraph function?
any ideas and suggestions?
class:
import { getGraph, getFloorplan, changeActiveCamera } from '../redux/actions';
const mapStateToProps = (state) => {
return {
currentSite: state.selection.currentSite,
currentCamera: state.selection.currentCamera,
};
};
function mapDispatchToProps(dispatch) {
return {
getGraph: (site) => dispatch(getGraph(site)),
getFloorplan: (site) => dispatch(getFloorplan(site)),
changeActiveCamera: (site, id) => dispatch(changeActiveCamera(site, id)),
};
}
loadGraph() {
if (this.props.currentSite) {
this.props.getFloorplan(this.props.currentSite.identif).then(() => {
console.log('Fetched floorplan');
this.props.getGraph(this.props.currentSite.identif).then(() => {
console.log('Fetched model', this.props.realGraph.model);
// new camera-related node & link status
if (this.props.currentCamera) {
this.props.changeActiveCamera(
this.props.currentSite.identif,
this.props.currentCamera.identif
);
}
});
});
}
}
converted from class to hooks:
Hooks:
const dispatch = useDispatch();
const realGraph = useSelector((state) => state.graphArticles.graph);
const currentSite = useSelector((state) => state.selection.currentSite);
const currentCamera = useSelector((state) => state.selection.currentCamera);
const dispatchGetFloorplan = (site) => dispatch(getFloorplan(site));
const dispatchGetGraph = (site) => dispatch(getGraph(site));
const dispatchChangeActiveCamera = (site, id) =>
dispatch(changeActiveCamera(site, id));
const loadGraph = () => {
if (currentSite) {
dispatchGetFloorplan(currentSite.identif).then(() => {
console.log('Fetched floorplan');
dispatchGetGraph(currentSite.identif).then(() => {
console.log('Fetched model', realGraph.model);
// new camera-related node & link status
if (currentCamera) {
dispatchChangeActiveCamera(
currentSite.identif,
currentCamera.identif
);
}
});
});
}
};
my action related to those:
export function getGraph(site) {
return getData(`api/graph/${site}`, GET_GRAPHS);
}
export function getFloorplan(site) {
return getImage(`api/graph/${site}/floorplan`, GET_FLOORPLAN);
}
On first glance, there are several things I would change in the code you provided.
First, don't use any wrapper factories over your dispatch functions. Use dispatch(action()) directly where you need it component. You aren't gaining anything by creating wrapper functions.
Second, it would be advisable to use some sort of middleware, like Redux Thunk, to handle async Redux actions (like fetching something from the API).
The actions you provided are just "dumb" functions, which are not returning promises so you can't expect it to be "then"-able in your target component.
I also advise the async/await syntax since it is much more readable.
Third, you need to leverage the Hooks reactive API with the useEffect hook.
So first try to define getFloorPlan and getGraph as async actions using the redux-thunk syntax.
export const getGraphAsync = (site) => async (dispatch) => {
try {
const data = await getData(`api/graph/${site}`, GET_GRAPHS);
dispatch(saveGraphData(data)) // save data into Redux store with a normal, synchronous action (plain object)
} catch (error) {
console.log(error)
}
}
export const getFloorplanAsync = (site) => async (dispatch) => {
try {
const data = await getImage(`api/graph/${site}/floorplan`, GET_FLOORPLAN);
dispatch(saveImageData(data)) // save data into Redux store with a normal, synchronous action (plain object)
} catch (error) {
console.log(error)
}
}
I am making an assumption that you correctly configured your store.js to use the thunk middleware.
And then refactor the rest of the component (following some best practices):
const someHookComponent = () => {
// ...
const dispatch = useDispatch();
const currentSite = useSelector((state) =>
state.selection.currentSite);
const currentCamera = useSelector((state) =>
state.selection.currentCamera);
const loadGraph = async () => {
if (currentSite) {
await dispatch(getFloorPlanAsync(currentSite.identif));
console.log('Fetched floorplan');
await dispatch(getGraphAsync(currentSite.identif));
console.log('Fetched model', realGraph.model); /* where is
realGraph coming from? */
/* Why is it important that these 2 dispatches follow one
another when there is no data being passed from one to the
other, or being used later in the component... */
});
});
}
};
useEffect(() => {
// new camera-related node & link status
if (currentCamera) {
dispatch(changeActiveCamera(
currentSite.identif,
currentCamera.identif
));
}
}, [currentSite?.identif, currentCamera?.identif]) /* null chaining is optional here */
// ...
}
I am guessing that loadGraph gets called by some onClick event somewhere down the line like this:
onClick={loadGraph}
If it is called inside useEffect, define the deps (variables used inside loadGraph):
useDeepCompareEffect(() => {
// ... some logic
loadGraph()
}, [currentSite, realGraph])
If you put your currentSite and currentCamera objects directly into the useEffect list of deps then you need to do a deep comparison "by hand".
In that case it's best to create a custom hook like useDeepCompareEffect which will do the heavy lifting of running deep comparisons of reference types under the hood (with the help of some library like lodash for example).
If you want to use or console.log the latest value of realGraph (reference type), you need to use the useEffect hook with a deep comparison again (or just extract the target primitive directly into the deps list and use vanilla useEffect) :
useDeepCompareEffect(() => {
if (realGraph) {
console.log('Fetched model', realGraph.model);
}
}, [realGraph]) // reference type
// or
useEffect(() => {
if (realGraph) {
console.log('Fetched model', realGraph.model);
}
}, [realGraph.someProperty]) // primitive
I'm trying to write tests for a method in my mobX store that calls a helper method getPages that makes an asynchronous API call to fetch some data. I was successfully able to mock axios when writing tests for the getPages helper function but now that I'm testing the mobx store I can't seems to mock axios anymore.
Is this because the store file doesn't import axios directly?
Do I have to mock getPages helper function instead? If so how would I do that?
Currently the API call await axios.get('${process.env.REACT_APP_BACKEND_URL}/pages'); is returning undefined instead of the mocked value. Ultimately I just need this call to returned the mocked value.
Thank you
MobX Store
import { makeAutoObservable } from 'mobx';
import { getPages } from 'lib/api';
class DocumentStore {
pages = [];
currentPage = null;
constructor() {
makeAutoObservable(this);
}
setPages = (pages) => {
this.pages = pages;
return this.pages;
};
setCurrentPage = (page) => {
this.currentPage = page;
return this.currentPage;
};
getPages = async (documentName) => {
const pageUrls = await getPages('a-test', documentName);
this.setCurrentPage(pageUrls[0]);
this.setPages(pageUrls);
};
}
export default DocumentStore;
API call
export const getPages = async () => {
let resp;
try {
resp = await axios.get(
`${process.env.REACT_APP_BACKEND_URL}/pages`,
);
} catch (err) {
throw new Error(err);
}
if (resp?.data?.urls) {
return resp.data.urls;
} else {
throw new Error('Unable to fetch Urls');
}
};
Test File
import DocumentStore from './index';
import axios from 'axios';
const TEST_PAGES = [
{
url: 'URL1',
},
{
url: 'URL2',
},
{
url: 'URL3',
},
];
const FETCH_PAGES_MOCK = {
data: {
urls: TEST_PAGES,
},
};
jest.mock('axios');
describe('DocumentStore', () => {
describe('getPages', () => {
axios.get.mockResolvedValue(FETCH_PAGES_MOCK);
it('fetches pages', () => {
const store = new DocumentStore();
expect(store.pages.length).toEqual(0);
store.getPages();
expect(store.pages.length).toEqual(3);
});
});
});
Mock indirect dependencies(axios.get()) and mock direct dependencies(getPages from lib/api module), both of them are ok. Depends on which modules you want as a unit. I prefer smaller units, only mock modules that the tested code directly depends on.
Too large a unit will make the test more complicated and difficult to locate the problem. The huge unit is close to higher level testing, such as integration testing, e2e testing.
For your case, you forgot to use await when calling the store.getPages() method.
E.g.
index.test.js:
jest.mock('axios');
describe('DocumentStore', () => {
describe('getPages', () => {
axios.get.mockResolvedValue(FETCH_PAGES_MOCK);
it('fetches pages', async () => {
const store = new DocumentStore();
expect(store.pages.length).toEqual(0);
await store.getPages('test');
expect(store.pages.length).toEqual(3);
expect(axios.get).toBeCalled();
});
});
});
I have a sendMail.js class content as below
export default function registerEmailHandlers() {
const sendMailAsync = async (emailAddress) => {
return await sendMail();
}
return {
sendMailAsync
}
}
How can I write Jest mock for this type of code?
I want mock sendMailAsync always return 'some test'.
I tried to research but nothing same that what I want.
Could you do something like
const mockSendMailAsync = jest.fn());
jest.mock('./path/to/file/sendMail', () => (
sendMailAsync = mockSendMailAsync
)
Then you could assert that mockSendMailAsync has been called
I'm having some problems with mocking, I've mocked a node module by adding a mocks/ssh2-sftp-client.ts file:
const mockSsh2SftpClient = jest.fn().mockImplementation(() => {
return {
connect: async () => {},
end: async () => {},
on: () => {}
}
})
export default mockSsh2SftpClient
This works, kinda. My tests run correctly using this mock, but in the tests SftpClient.mock.instances[0] is an empty mockConstructor {} object instead of this mock (ie. SftpClient.mock.instances[0].end is undefined). What am I doing wrong?
for reference, my testing code looks like this:
import { ConnectConfig } from 'ssh2'
import SftpClient from 'ssh2-sftp-client'
import { withSftp } from '../sftp'
// Type assertion to make TypeScript happy.
const MockSftpClient = SftpClient as jest.Mock<SftpClient>
describe(withSftp, () => {
const mockConnectionConfig: ConnectConfig = {}
beforeEach(() => {
// Clear all instances and calls to constructor and all methods:
MockSftpClient.mockClear()
})
it('should call the callback after successfully connecting', async () => {
const mockCallback = jest.fn()
// Instantiates SftpClient and calls connect, then the callback, then end.
await withSftp(mockConnectionConfig, mockCallback)
const mockInstance = MockSftpClient.mock.instances
expect(mockCallback).toHaveBeenCalledTimes(1)
expect(MockSftpClient.mock.instances[0].end).toHaveBeenCalledTimes(1)
})
})
The last fails because MockSftpClient.mock.instances[0].end is undefined, where it should be a function.
The mock constructor provided by Jest only records this as the instance so if your mock constructor returns a different object then that object won't be recorded in the instances array.
To get the behavior you are wanting just mock with a standard function and use this:
__mocks__/ssh2-sftp-client.ts
const mockSsh2SftpClient = jest.fn(function() {
this.connect = jest.fn();
this.end = jest.fn();
this.on = jest.fn();
});
export default mockSsh2SftpClient