React and jest mock module - javascript

I am creating an application in which I use redux and node-fetch for remote data fetching.
I want to test the fact that I am well calling the fetch function with a good parameter.
This way, I am using jest.mock and jasmine.createSpy methods :
it('should have called the fetch method with URL constant', () => {
const spy = jasmine.createSpy('nodeFetch');
spy.and.callFake(() => new Promise(resolve => resolve('null')));
const mock = jest.mock('node-fetch', spy);
const slug = 'slug';
actionHandler[FETCH_REMOTE](slug);
expect(spy).toHaveBeenCalledWith(Constants.URL + slug);
});
Here's the function that I m trying to test :
[FETCH_REMOTE]: slug => {
return async dispatch => {
dispatch(loading());
console.log(fetch()); // Displays the default fetch promise result
await fetch(Constants.URL + slug);
addLocal();
};
}
AS you can see, I am trying to log the console.log(fetch()) behavior, and I am having the default promise to resolve given by node-fetch, and not the that I've mock with Jest and spied with jasmine.
Do you have an idea what it doesn't work ?
EDIT : My test displayed me an error like my spy has never been called

Your action-handler is actually a action handler factory. In actionHandler[FETCH_REMOTE], you are creating a new function. The returned function taskes dispatch as a parameter and invokes the code you are showing.
This means that your test code will never call any function on the spy, as the created function is never invoked.
I think you will need to create a mock dispatch function and do something like this:
let dispatchMock = jest.fn(); // create a mock function
actionHandler[FETCH_REMOTE](slug)(dispatchMock);
EDIT:
To me, your actionHandler looks more like an actionCreator, as it is usually called in redux terms, though I personally prefer to call them actionFactories because that is what they are: Factories that create actions.
As you are using thunks(?) your actionCreater (which is misleadingly named actionHandler) does not directly create an action but another function which is invoked as soon as the action is dispatched. For comparison, a regular actionCreator looks like this:
updateFilter: (filter) => ({type: actionNames.UPDATE_FILTER, payload: {filter: filter}}),
A actionHandler on the other hand reacts to actions being dispatched and evaluates their payload.
Here is what I would do in your case:
Create a new object called actionFactories like this:
const actionFactories = {
fetchRemote(slug): (slug) => {
return async dispatch => {
dispatch(loading());
console.log(fetch()); // Displays the default fetch promise result
let response = await fetch(Constants.URL + slug);
var responseAction;
if (/* determine success of response */) {
responseAction = actionFactories.fetchSuccessful(response);
} else {
responseAction = actionFactories.fetchFailed();
}
dispatch(responseAction);
};
}
fetchFailed(): () => ({type: FETCH_FAILED, }),
fetchSuccessful(response): () => ({type: FETCH_FAILED, payload: response })
};
Create an actionHandler for FETCH_FAILED and FETCH_SUCCESSFUL to update the store based on the response.
BTW: Your console.log statement does not make much sense too me, since fetch just returns a promise.

Related

How to access data outside of a Promise

I'm making a react app that sends an API call to OpenWeather to get the weather data for a city (specified by the user). Here's what the request for that call looks like:
async function getAPI() {
const apiCall = await axios.get(apiLink).then(res => {
res = {
temp : res.data.main.temp - 273.15,
weatherIcon : res.data.weather[0].icon,
windSpeed : res.data.wind.speed
}
return res
});
return apiCall
}
const weatherData = getAPI()
Notice that I try to store the data I want from the API response in a variable called weatherData. That way I can simply call that variable whenever I need, heres an example of HTML code that uses this variable:
<p>
temperature is {weatherData.temp} Celcius
</p>
This results in weatherData.temp simply not showing up on the browser side for some reason. A console.log(weatherData) prints this in the console:
Promise {<pending>}
[[Prototype]]: Promise
[[PromiseState]]: "fulfilled"
[[PromiseResult]]: Object
temp: 29.53
weatherIcon: "04d"
windSpeed: 1.59
[[Prototype]]: Object
How do I extract the data from the promise in a way that allows me to easily refer to said data for use in HTML code?
Answer below is if you are using functional components and react hooks.
You can can go two directions:
Using a try catch block:
const fetchWeather = async () => {
try {
const res = await axios.get(apiLink);
console.log(res);
setWeather(res.data); //Im not sure what the exact response is, but you can access the keys you need.
// you can then set the data you need to your state to render it.
} catch (error) {
// handle error
}
}
Or you can use .then .catch
const fetchWeather = async () => {
axios.get(apiLink)
.then((res) => {
setWeather(res.data); //Im not sure what the exact response is, but you can access the keys you need.
// set the data you need from the respones to your state.
})
.catch((err) => {
// handle error
})
}
In both cases you can just call the function in your useEffect hook.
useEffect(() => {
fetchWeather()
}, [])
In general my preference goes to set the response you get from the Api into the local state (meaning the state of your page/component). And then rendering the state to your jsx.
So if you are using react hooks, your state could look like this:
const [weather, setWeather] = useState({});
Last Edit:
Finally you can just refer to your state within your jsx/html. Assuming your weather state looks like this:
{
temp: '50 degrees'
}
In your JSX you can just refer to it this way:
<>
<div>{weather.temp}</div>
</>

Trigger mocked callback argument with Jest

Using Jest, I'd like to trigger the configure callback argument. If I were to write this with a sinon stub, I could do something like configure.yields('my value'). Does Jest have anything similar? To help illustrate what I'm after, I created a simple example.
I've imported mymodule, instantiated, and called the configure function. I'd like my test to trigger this callback. i.e. (err, results) => ...
import MyModule from 'mymodule';
export function execute(key, value) {
return new Promise((resolve, reject) => {
new MyModule().configure(key, value, (err, result) => {
// I need my test to trigger this section of code...
return resolve('my resolved value')
})
})
}
This test is mocking MyModule and setting configure to a jest.fn(). While I have everything mocked, I'm not able to specify the configure args and trigger a specific argument. Ideally, I'd like to do something like mockFn.yields('my value') to trigger the configure callback.
import MyModule from 'mymodule';
jest.mock('mymodule');
describe('Test Example', () => {
test('should trigger mocked args callback', async () => {
const mockFn = jest.fn();
const key = 'my_key';
const value = 'my_value';
const actual = require('./src/my-service');
MyModule.mockImplementation(() => {
return {
configure: mockFn
};
});
await actual.execute(key, value)
// how can I trigger the mocked configure argument callback?
});
});
Test currently fails because it cannot trigger the callback function. Error states: Async callback was not invoked
The error means that a promise that test async function returns wasn't settled. This happens because execute returns a pending promise, mocked configure doesn't call a callback that is supposed to resolve it.
configure should be mocked correctly and behave the same way as original implementation regarding callback argument:
mockFn.mockImplementation((key, value, cb) => cb(null, 'some result'));
const promise = actual.execute(key, value);
expect(mockFn).toBeCalledWith(key, value, expect.any(Function));
await expect(promise).resolves.toBe('my resolved value');

How to mock const method in jest?

I unit test code in typescript, use jest. Please teach me how to mock getData to return the expected value. My code as below:
// File util.ts
export const getData = async () => {
// Todo something
return data;
}
// File execution.ts import { getData } from './util';
function execute()
{
// todo something
const data = await getData();
// todo something
}
The problem is that your function returns a promise. Depends on how you use it there are several ways to mock it.
The simplest way would be to mock it directly, but then it will always return the same value:
// note, the path is relative to your test file
jest.mock('./util', () => ({ getData: () => 'someValue' }));
If you want to test both the resolved and the rejected case you need to mock getData so it will return a spy where you later on can change the implementation use mockImplementation. You also need to use async/await to make the test work, have a look at the docs about asynchronous testing:
import { getData } from './util';
jest.mock('./util', () => ({ getData: ()=> jest.fn() }));
it('success case', async () => {
const result = Promise.resolve('someValue');
getData.mockImplementation(() => result);
// call your function to test
await result; // you need to use await to make jest aware of the promise
});
it('error case', async () => {
const result = Promise.reject(new Error('someError'));
getData.mockImplementation(() => result);
// call your function to test
await expect(result).rejects.toThrow('someError');
});
Try the following in your test file.
Import the function from the module.
import { getData } from './util';
Then mock the module with the function and its return value after all the import statements
jest.mock('./util', () => ({ getData: jest.fn() }))
getData.mockReturnValue("abc");
Then use it in your tests.
Because mocking expression functions can be a real pain to get right, I'm posting a full example below.
Scenario
Let's say we want to test some code that performs some REST call, but we don't want the actual REST call to be made:
// doWithApi.ts
export const doSomethingWithRest = () => {
post("some-url", 123);
}
Where the post is a function expression in a separate file:
// apiHelpers.ts
export const post = (url: string, num: number) => {
throw Error("I'm a REST call that should not run during unit tests!");
}
Setup
Since the post function is used directly (and not passed in as a parameter), we must create a mock file that Jest can use during tests as a replacement for the real post function:
// __mocks__/apiHelpers.ts
export const post = jest.fn();
Spy and Test
Now, finally inside the actual test, we may do the following:
// mockAndSpyInternals.test.ts
import {doSomethingWithRest} from "./doWithApi";
afterEach(jest.clearAllMocks); // Resets the spy between tests
jest.mock("./apiHelpers"); // Replaces runtime functions inside 'apiHelpers' with those found inside __mocks__. Path is relative to current file. Note that we reference the file we want to replace, not the mock we replace it with.
test("When doSomethingWithRest is called, a REST call is performed.", () => {
// If we want to spy on the post method to perform assertions we must add the following lines.
// If no spy is wanted, these lines can be omitted.
const apiHelpers = require("./apiHelpers");
const postSpy = jest.spyOn(apiHelpers, "post");
// Alter the spy if desired (e.g by mocking a resolved promise)
// postSpy.mockImplementation(() => Promise.resolve({..some object}))
doSomethingWithRest();
expect(postSpy).toBeCalledTimes(1)
expect(postSpy).toHaveBeenCalledWith("some-url", 123);
});
Examples are made using Jest 24.9.0 and Typescript 3.7.4

How to properly test a debounced function in an asynchronous React component?

My react component runs an asynchronous query to obtain some data using lodash debounce - since user input can cause re-queries and I want to rate-limit the queries - and then sets the state of the component with the results that are returned.
MyComponent (React Component)
componentWillMount() {
this.runQuery();
}
handler = (response) => {
this.setState({ results: response.results });
}
runQuery = _.debounce((props = this.props) => {
// run the query
doStuff(mainParams)
.withSomeOtherStuff(moreParams)
.query()
.then(this.handler)
.catch((error) => {
this.props.catchError(error);
});
}, 200);
I am currently stubbing out my main api exit point that goes out and fetches the data which returns a promise thanks to the sinon-stub-promise package
before((done) => {
stub = stubGlobalFn('evaluate'); // returns stubbed promise, uses npm:sinon-stub-promise
});
This allows me the ability to use my custom Reader (tested elsewhere) to read in a mock response and then resolve it synchronously for testing purposes.
mytest.spec.jsx
let stub;
const testWithProps = props => (
new Promise((resolve, reject) => {
new Reader(histories).readGrid((err, grid) => {
try {
expect(err).to.be.a('null');
stub.resolves(grid);
// ....
Then in the same testWithProps function I'm able to mount the Table component with the props I specify in my test as sort of a test factory.
const wrapper = mount(<Table {...props} />);
And here's where I run into my confusion, I have stubbed out the promise that gets resolved when the main evaluate async function is invoked but not the state handler.
stub.thenable.then(() => {
// --------------------------
// PROBLEM: how to test without setting a timeout?
// --------------------------
setTimeout(() => {
resolve(wrapper.update());
}, 200);
// --------------------------
// --------------------------
});
Should I be stubbing my handler function inside of my react component instead if I'm wanting to test the state of the component after the async behavior? I'm not sure how to even stub that part out if that's even what's needed.
Ultimately my test looks like this by the end:
it('toggles the row for the value when clicked', () => {
const props = {
// some props that I use
};
return testWithProps(props).then((wrapper) => {
// simply testing that my mocked response made it in successfully to the rendered component
expect(wrapper.state().results.length).to.equal(4);
});
});

Jest not recognizing spy is called

I'm having a little trouble confirming my function is called using Jest. I've got two mocked functions. One is just mocked to return a promise, the other is a simple spy that should be called in the then() block of the first.
Below, in the test, you will see two expectations. The first expectation passes. The second does not. i.e. expect(sendSpy).toHaveBeenCalled() passes but expect(sendCallbackSpy).toHaveBeenCalled() does not.
However, when I put the console.log statements in the file (as below), both execute (i.e. the 'before' and 'after'), and if I console log the window.props.onSend it confirms that the mocked function is present. So it looks like the function should be called.
Another thing of note is that my implementation requires me to pass in the callback in a props object from my window. Further, to mock things on the window in Jest, you just mock global as I'm doing below. I don't think that is relevant to the issue but worth pointing out nonetheless.
Could it be that the expectation is run before the actual function is called in the then() block?
export class MyButton extends Component {
handleClick = () => {
this.props.send(this.props.url).then(res => {
console.log('before', window.props.onSend)
window.props.onSend(res.data)
console.log('after')
})
}
render() {
return <button onClick={handleClick} />
}
}
//test
test('it calls the identity function when button is clicked', () => {
const sendSpy = jest.fn(() => { return Promise.resolve({ data: 'hello' }) })
const sendCallbackSpy = jest.fn()
global.props = { onSend: sendCallbackSpy }
wrapper = shallow(<MyButton send={sendSpy} } />)
const button = wrapper.find('button')
button.simulate('click')
expect(sendSpy).toHaveBeenCalled()
expect(sendCallbackSpy).toHaveBeenCalled()
})
You need to wait for the promise before testing the second spy:
test('it calls the identity function when button is clicked', async() => {
const request = Promise.resolve({ data: 'hello' })
const sendSpy = jest.fn(() => request)
const sendCallbackSpy = jest.fn()
global.props = { onSend: sendCallbackSpy }
wrapper = shallow(<MyButton send={sendSpy} } />)
const button = wrapper.find('button')
button.simulate('click')
expect(sendSpy).toHaveBeenCalled()
await request
expect(sendCallbackSpy).toHaveBeenCalled()
})

Categories