Mock the internals of a thunk - reactjs

This is a test I'm running:
it('dispatches the logout action', () => {
const store = mockStore({});
store.dispatch(logout()); // TODO: logout() has a function in its payload that gives an error
const expectedActions = store.getActions();
expect(expectedActions).toMatchSnapshot();
});
It's giving me the following error: (using this library: https://github.com/wix/react-native-navigation)
Navigation.getRegisteredScreen: login used but not yet registered
The problem is that the logout() action dispatches a thunk (async) that calls Navigation.startSingleScreenApp. I somehow need to mock this Navigation class OR mock the entire logout() action.
I've tried several things:
loginService.logout = jest.fn();
jest.spyOn(Navigation, 'startSingleScreenApp');
But none of these seem to work.
Can anyone help me? I'm familiar with mocking but I'm clueless here.

It would be helpful to look at the definition of logout(). You could fake the implementation of all the methods called within logout(). I'm not sure what the return value of startSingleScreenApp is, but you could do:
jest.spyOn(Navigation, 'startSingleScreenApp').mockImplementation(() => {
/* do something fake */
});

Related

Why is dispatch returning a promise with no result? (redux middleware)

I came into a React project that uses vanilla Redux with middleware. The way it is setup is as follows:
composeEnhancers(applyMiddleware(...middleware.map(f => f(services))))
Now middlware is an array of, well, middleware containing functions. services is an object containing external services that are injected into the middlware functions (api and so on).
The interesting part is the middleware, here is a sample of it:
...
const throwErrorFlow = ({ api }) => ({ dispatch, getState }) => next => async (action) => {
next(action)
if (action.type === actions.THROW_ERROR) {
try {
dispatch(actions.setLoadingSlot({ state: false, context: action.payload.context }))
const context = getState().ui.context
const payload = { location: action.payload.location, error: action.payload.error?.stack, context }
console.log(payload);
await api.context.throwError(payload)
dispatch(actions.setErrorModalVisibility({ payload, visibility: true }))
} catch (error) {
console.log(error);
}
}
}
const middleware = [
middlwareFunction1,
middlwareFunction2,
...
throwErrorFlow
]
export default middleware
Then I created my own test action that returns a test string. I added a similar middlware function as the rest. When dispatching this test action from the UI and logging its result, all I get is: Promise {<fulfilled>: undefined}
So I tried zooming in a bit. My action is just the following:
export const customAction = payload => ({
type: CUSTOM_ACTION,
payload: payload,
})
And my bit in the middleware is the following:
const customAsyncActionFlow = () => storeAPI => () => action => {
if (action.type === actions.CUSTOM_ACTION) {
console.log(action);
return 'TEST!'
}
}
const middleware = [
middlwareFunction1,
middlwareFunction2,
...
throwErrorFlow,
customActionFlow
]
export default middleware
And I call it from the UI as:
console.log(dispatch(customAction('Hello World!')));
My action is logged correctly to the console, but then I get Promise {<fulfilled>: undefined} instead of 'TEST!'. So I removed all other middleware functions and only kept my customActionFlow, and everything worked as I expected. Where is this Promise with no result coming from? Yes all other middleware functions do not return anything, they just modify the state. Does this have to do with this fact? And how do I 'fix' this?
EDIT: okay so I seem to understand what is going on. For each action that requires interaction with the api, a middleware is written for this action which gets applied. In the end there are 20 middleware functions all culminating with the async action for each one. The action that I defined with the test middleware that returns a value gets "lost" in the mix I guess? I am still not sure as to why my return has no effect whatsoever.
Is there a way to make my dispatch action call the my test middleware exclusively while keeping all other middlewares applied?
Oh dear. While this isn't a direct answer to your question...
I've seen that style of "write all Redux logic as custom middleware" tried a few times... and it is a bad idea!
It makes things highly over-complicated, and adding all these extra middleware for individual chunks of functionality adds a lot of overhead because they all have to run checks for every dispatched action.
As a Redux maintainer I would strongly recommend finding better approaches for organizing and defining the app logic. See the Redux Style Guide for our general suggestions:
https://redux.js.org/style-guide/
Now, as for the actual question:
When you call store.dispatch(someAction), the default behavior is that it returns the action object.
When you write a middleware, that can override the return value of store.dispatch(). A common example of this is the redux-thunk middleware, which just does return thunkFunction(dispatch, getState). This is commonly used to let thunks return promises so that the UI knows when some async logic is complete:
https://redux.js.org/tutorials/essentials/part-5-async-logic#checking-thunk-results-in-components
https://redux.js.org/tutorials/fundamentals/part-7-standard-patterns#thunks-and-promises
In this case, the middleware is itself defined as an async function, and every async function in JS automatically returns a promise. So, just having one async middleware in the chain is going to end up returning a promise from store.dispatch(anything). (This would be another reason to not write a bunch of logic directly in a custom middleware like that.)

Mocking an asyc function in react using Jest

I am new to react and jest.
I am getting stuck on the right way to mock an async function even after scouring many articles on this.
here is my scenario. I am pasting the code which is giving me trouble. I have the following function defined. I want to mock the getToken() function. The returned token is a string.
export async getSignin() {
const token = await getToken()
//do something with this token
}
export async function getToken(){
const token = (await accessToken())
return token
}
Test code:
it(" returns a valid user ", async () => {
const getToken = jest
.fn()
.mockImplementation(async () => Promise.resolve("abcd"))
const signedin = await getSignin()
}
when I do this, my expectation is that the code will use the mock implementation of the getToken and proceed. What I am getting is that it is throwing an error at accessToken(). My understanding of mock is that it should not go into the actual implementation and call accessToken()
what am I doing wrong here?
fetch-mock is a great package for mocking API requests in your test files! http://www.wheresrhys.co.uk/fetch-mock
After installing, in your beforeEach block in a test file you can now mock the payload from API calls. It will look something like this:
beforeEach(() => {
fetchMock.mock('/api/users'/1, { id: 1, name: 'Test User Name', address: 'Test User Address'})
})

Understanding async dispatch actions using redux thunk

I was just going though the files that dispatches ACTIONS in a reactjs app and basically came across the following function::-
// When app inits
export const init = () => async dispatch => {
dispatch({ type: TYPES.SET_LOADING });
await dispatch(getConfig());
await dispatch(getGenres());
dispatch({ type: TYPES.REMOVE_LOADING });
};
I am a bit confused as to what the async is doing ahead of the dispatch , i am used to seeing normal vanilla javascript async functions like so:
anasyncfunc = async () => {
// write you're code here
}
But the above code confuses me a little. Can somebody please explain this to me.
A function which uses the await keyword must me marked with async which you have pointed out in your example
anasyncfunc = async () => {
// write you're code here
}
In this function we can now use await since it was marked with async.
In the case of the thunk, we are effectively creating an outer function called init which when called will return an anonymous function. This anonymous function will accept dispatch as an argument and will make use of the await keyword, and as such needs to be marked with the async keyword.
In short, we really are just creating a function which will want to use await and therefor must be marked with async.
I hope this clarifies it.

integration tests - redux/react + nock.js

I have no clue how to find a way to write this integration test.
I am using enzyme for mocking react components, jest for testing and nock for mocking axios api calls.
So far I created test which simulate clicking on button and I would like to mock the api call.
In the internet there is no much help.
My test:
it('Should invoke clear action and clear the group', (done) => {
// GIVEN
const clearButtonComponent = wrapper.find('[id="123"]');
nock('http://localhost:8080')
.intercept('/path/api/brum/123/group', 'DELETE')
.reply(200, {
status: 200,
message: 'cleared',
});
const service = new myService();
// WHEN
clearButtonComponent.first().simulate('click');
const result = Promise.resolve(service.clearGroup(123));
// THEN
expect(result).toEqual({ x: 'x' }); // I know it's not what I expect
wrapper.update();
done();
});
async action redux:
export const clearGroup = id=> (dispatch, getState) => {
myService.clearGroup(id)
.then(() => {
return dispatch(getGroup(id))
});
};
method in myService:
clearGroup(id) {
return this._delete(`/${id}/group`);
}
of course path is more complex but my service extends base service which has this base url.
Can anybody tell me how to mock it to let code goes further?
It still complain that id is undefined - look like nock does not mock it.
I would drop nock (I try to only use it for testing clients these days) and mock myService with jest.
I don't use axios, so haven't used this, but it might do the trick.. https://github.com/knee-cola/jest-mock-axios.
Otherwise you could look at writing your own mock.. https://jestjs.io/docs/en/es6-class-mocks

Return value of a mocked function does not have `then` property

I have the following async call in one of my React components:
onSubmit = (data) => {
this.props.startAddPost(data)
.then(() => {
this.props.history.push('/');
});
};
The goal here is to redirect the user to the index page only once the post has been persisted in Redux (startAddPost is an async action generator that sends the data to an external API using axios and dispatches another action that will save the new post in Redux store; the whole thing is returned, so that I can chain a then call to it in the component itself). It works in the app just fine, but I'm having trouble testing it.
import React from 'react';
import { shallow } from 'enzyme';
import { AddPost } from '../../components/AddPost';
import posts from '../fixtures/posts';
let startAddPost, history, wrapper;
beforeEach(() => {
startAddPost = jest.fn();
history = { push: jest.fn() };
wrapper = shallow(<AddPost startAddPost={startAddPost} history={history} />);
});
test('handles the onSubmit call correctly', () => {
wrapper.find('PostForm').prop('onSubmit')(posts[0]);
expect(startAddPost).toHaveBeenLastCalledWith(posts[0]);
expect(history.push).toHaveBeenLastCalledWith('/');
});
So I obviously need this test to pass, but it fails with the following output:
● handles the onSubmit call correctly
TypeError: Cannot read property 'then' of undefined
at AddPost._this.onSubmit (src/components/AddPost.js:9:37)
at Object.<anonymous> (src/tests/components/AddPost.test.js:25:46)
at process._tickCallback (internal/process/next_tick.js:109:7)
So how can I fix this? I suspect this is a problem with the test itself because everything works well in the actual app. Thank you!
Your code is not testable in the first place. You pass in a callback to the action and execute it after saving the data to the database like so,
export function createPost(values, callback) {
const request = axios.post('http://localhost:8080/api/posts', values)
.then(() => callback());
return {
type: CREATE_POST,
payload: request
};
}
The callback should be responsible for the above redirection in this case. The client code which uses the action should be like this.
onSubmit(values) {
this.props.createPost(values, () => {
this.props.history.push('/');
});
}
This makes your action much more flexible and reusable too.
Then when you test it, you can pass a stub to the action, and verify whether it is called once. Writing a quality, testable code is an art though.
The problem with your code is that the startAddPost function is a mock function which does not return a Promise, but your actual this.props.startAddPost function does return a Promise.
That's why your code works but fails when you try to test it, leading to the cannot read property.... error.
To fix this make your mocked function return a Promise like so -
beforeEach(() => {
startAddPost = jest.fn().mockReturnValueOnce(Promise.resolve())
...
});
Read more about mockReturnValueOnce here.

Resources