Redux-observable: failed jest test for epic - reactjs

I followed the steps from documentation to test epic.
...
store.dispatch({ type: FETCH_USER });
expect(store.getActions()).toEqual([
{ type: FETCH_USER },
{ type: FETCH_USER_FULFILLED, payload }
]);
...
But I get failed because second action is been received some later like following.
Test failed
Expected value to equal:
[{"type": "FETCH_USER"}, {"type": "FETCH_USER_FULFILLED", "payload": [some]}]
Received:
[{"type": "FETCH_USER"}]
Difference:
- Expected
+ Received
## -1,20 +1,5 ##
Array [
Object {"type": "FETCH_USER"},
Object {"type": "FETCH_USER_FULFILLED", "payload": [some]} ] // this is what should be.
So I think I should know when the dispatch is finished or some like that.
How can I solve this?
I used fetch() and Rx.Observable.fromPromise instead of ajax.getJSON()
Here is my epic.
const fetchUserEpic = (action$) =>
action$
.ofType(FETCH_USER)
.mergeMap(() => {
return Rx.Observable.fromPromise(api.fetchUser())
.map((users) => ({
type: FETCH_USER_FULFILLED,
payload: { users }
}))
.catch((error) => Rx.Observable.of({
type: FETCH_USER_ERROR,
payload: { error }
}))
.takeUntil(action$.ofType(FETCH_USER_CANCELLED))
})

The reason is that promises always resolve on the next microtask so your api.fetchUser() isn't emitting synchronously.
You'll need to either mock it out, use something like Promise.resolve().then(() => expect(store.getActions).toEqual(...) to wait until the next microtask, or you can experiment with testing your epics directly without using redux.
it('Epics with the appropriate input and output of actions', (done) => {
const action$ = ActionsObservable.of({ type: 'SOMETHING' });
somethingEpic(action$, store)
.toArray() // collects everything in an array until our epic completes
.subscribe(actions => {
expect(actions).to.deep.equal([
{ type: 'SOMETHING_FULFILLED' }// whatever actions
]);
done();
});
});
This will be our preferred testing story in the docs when I (or someone else) has time to write them up. So instead of using redux and the middleware in your tests, we just call the epic function directly with our own mocks. Much easier and cleaner.
With that approach, we can leverage the new dependency injection feature of redux-observable: https://redux-observable.js.org/docs/recipes/InjectingDependenciesIntoEpics.html
import { createEpicMiddleware, combineEpics } from 'redux-observable';
import { ajax } from 'rxjs/observable/dom/ajax';
import rootEpic from './somewhere';
const epicMiddleware = createEpicMiddleware(rootEpic, {
dependencies: { getJSON: ajax.getJSON }
});
// Notice the third argument is our injected dependencies!
const fetchUserEpic = (action$, store, { getJSON }) =>
action$.ofType('FETCH_USER')
.mergeMap(() =>
getJSON(`/api/users/${payload}`)
.map(response => ({
type: 'FETCH_USER_FULFILLED',
payload: response
}))
);
import { ActionsObservable } from 'redux-observable';
import { fetchUserEpic } from './somewhere/fetchUserEpic';
const mockResponse = { name: 'Bilbo Baggins' };
const action$ = ActionsObservable.of({ type: 'FETCH_USERS_REQUESTED' });
const store = null; // not needed for this epic
const dependencies = {
getJSON: url => Observable.of(mockResponse)
};
// Adapt this example to your test framework and specific use cases
fetchUserEpic(action$, store, dependencies)
.toArray() // buffers all emitted actions until your Epic naturally completes()
.subscribe(actions => {
assertDeepEqual(actions, [{
type: 'FETCH_USER_FULFILLED',
payload: mockResponse
}]);
});

First, use isomorphic-fetch instead of Observable.ajax for nock support, like this
const fetchSomeData = (api: string, params: FetchDataParams) => {
const request = fetch(`${api}?${stringify(params)}`)
.then(res => res.json());
return Observable.from(request);
};
So my epic is:
const fetchDataEpic: Epic<GateAction, ImGateState> = action$ =>
action$
.ofType(FETCH_MODEL)
.mergeMap((action: FetchModel) =>
fetchDynamicData(action.url, action.params)
.map((payload: FetchedData) => fetchModelSucc(payload.data))
.catch(error => Observable.of(
fetchModelFail(error)
)));
Then, you may need an interval to decide when to finish the test.
describe("epics", () => {
let store: MockStore<{}>;
beforeEach(() => {
store = mockStore();
});
afterEach(() => {
nock.cleanAll();
epicMiddleware.replaceEpic(epic);
});
it("fetch data model succ", () => {
const payload = {
code: 0,
data: someData,
header: {},
msg: "ok"
};
const params = {
data1: 100,
data2: "4"
};
const mock = nock("https://test.com")
.get("/test")
.query(params)
.reply(200, payload);
const go = new Promise((resolve) => {
store.dispatch({
type: FETCH_MODEL,
url: "https://test.com/test",
params
});
let interval: number;
interval = window.setInterval(() => {
if (mock.isDone()) {
clearInterval(interval);
resolve(store.getActions());
}
}, 20);
});
return expect(go).resolves.toEqual([
{
type: FETCH_MODEL,
url: "https://test.com/assignment",
params
},
{
type: FETCH_MODEL_SUCC,
data: somData
}
]);
});
});
enjoy it :)

Related

how to make a dispatch wait for the first dispatch to run

i have 2 dispatch inside the useEffect, is there a way i can make the bottom dispatch to wait for the first one to run. i need to get the post._id from the first to pass to the second. please doesn't anyone have an idea on how to solve this?
useEffect(() => {
dispatch(getPost(params.slug));
dispatch(listComment(post._id));
}, [dispatch]);
// getPost action
export const getPost = (slug) => async(dispatch) => {
try {
dispatch({
type: types.POST_REQUEST
});
const {
data
} = await axios.get(`http://localhost:8080/api/posts/${slug}`);
dispatch({
type: types.POST_SUCCESS,
payload: data,
});
} catch (err) {
dispatch({
type: types.POST_FAIL,
payload: err.response && err.response.data.message ?
err.response.data.message :
err.message,
});
}
};
You can introduce a loading state for Post, and use that in another useEffect to achieve that
useEffect(() => {
dispatch(getPost(params.slug));
}, [dispatch]);
useEffect(() => {
if(!post.loading) {
dispatch(listComment(post._id));
}
}, [dispatch, post.loading]);
It's possible to coordinate this by observing loading flags with useEffect, but a simpler solution in my opinion is to extend the thunk to dispatch another action after the response for the post is available:
// actions:
export const listComments = (postId) => async (dispatch) => {
/* thunk that fetches comments on a post */
}
// Fetch post for slug, optionally with comments.
export const getPost = (slug, withComments = false) => async(dispatch) => {
dispatch({ type: types.POST_REQUEST });
try {
const { data } = await axios.get(`http://localhost:8080/api/posts/${slug}`);
dispatch({ type: types.POST_SUCCESS, payload: data });
if (withComments) {
dispatch(listComments(data.id));
}
} catch (err) {
const payload = err.response && err.response.data.message ?
err.response.data.message : err.message;
dispatch({ type: types.POST_FAIL, payload });
}
};
// In the component:
dispatch(getPost(slug, true));
you can try with below snippet, also will this post.id get after first dipatch or it will be present?
useEffect(() => {
dispatch(getPost(params.slug));
}, [required dispaencies]);
useEffect(() => {
if(!post._id || some loading){
dispatch(listComment(post._id));
}
},[required depencies])

Is there a way to mock refetch in MockedProvider - Apollo Client?

Here is how I am using MockedProvider. How can I mock refetch in mocks array?
const mocks = [{
request: {
query: GET_USERS_BY_FACILITY,
variables: {
facility: 300
}
},
result: {
data: {
GetUsersByFacility: [{
nuId: 'Q916983',
userName: faker.internet.userName(),
profileKey: 'testKey',
profileValue: 'testValue',
__typename: 'FacilityUser'
}]
}
},
refetch: () => {
return {
data: {
GetUsersByFacility: [{
nuId: 'Q916983',
userName: faker.internet.userName(),
profileKey: 'testKey',
profileValue: 'testValue',
__typename: 'FacilityUser'
}]
}
}
}
}
This test case calls refetch function when delete event is triggered.
it('should be able to click on delete user', async () => {
const {getByTestId} = render(
<MockedProvider mocks={mocks}>
<Users selectedFacility={300}/>
</MockedProvider>)
await wait(0)
fireEvent.click(getByTestId('btnDelete'))
})
I have been trying different ways, none seems to work. I get error message as TypeError: Cannot read property 'refetch' of undefined.
Thank you very much in hope of an answer.
Regards,
--Rajani
Maybe it's a bit late to answer, but if you have't got any answers yet, you would refer to the way I solved.
Please note that this might not be the correct answer.
You can find this code in react-apollo docs
const mocks = [
{
request: {
query: GET_DOG_QUERY,
variables: {
name: 'Buck',
},
},
result: () => {
// do something, such as recording that this function has been called
// ...
return {
data: {
dog: { id: '1', name: 'Buck', breed: 'bulldog' },
},
}
},
},
];
I make my refetch testcode based on this phrase // do something, such as recording that this function has been called
This is my mock example.
let queryCalled = false
const testingData = (value) => ({
data: {....}
})
const TESTING_MOCK = {
request: {
query: TESTING_QUERY,
variables: { some: "variables" },
},
result: () => {
if (queryCalled) return testingData("refetched");
else {
queryCalled = true;
return testingData("first fetched");
}
},
};
This component refetches data when the button is clicked. I designed my test code in this order
when it's rendered for the first time, it fetches the mock data .
=> In the code above, queryCalled is false so, it reassigns queryCalled as true and return the "first fetched" mock data,
when a click event occurs, refetch occurs too.
=> On the same principle the mock data returns "refetched" mock data.
My testcode example is here
it("refetch when clicked save button.", async () => {
const mocks = [TESTING_MOCK];
let utils: RenderResult = render(<SomeTestingComponent mocks={mocks} />);
await waitForNextTick(); //for getting a data, not loading
const titleInput = utils.getByDisplayValue("first fetched");
const saveBtn = utils.getByText("save");
fireEvent.click(saveBtn);
await waitForElement(() => utils.getByDisplayValue("refetched"));
})
Please let me know if you have any other suggestions!
For anyone that might still run into this, the solution to make refetch work in your tests is to use the newData method while keeping track of the query having been called.
I don't know if this is a bug in the MockedProvider implementation, but I was banging my head against the wall trying to make newData work together with result, but it turns out that newData completely overrides result.
A working solution (tested with useQuery and Apollo Client 3) would be something like this:
let queryCalled = false;
const refetchMock = {
request: {
query: YOUR_QUERY
},
newData: () => {
if (queryCalled) {
return {
data: {
// your refetched data
}
};
} else {
queryCalled = true;
return {
data: {
// your first fetched data
}
};
}
}
};
The newData solution didn't work for me with apollo client #2.6.
As a workaround, for the few tests that utilize refetch I had to physically mock the useQuery function and provide mock functions for the return of refetch; for our custom hook (where an overridden useQuery hook is exported as default), it looked something like this:
import * as useQueryModule from '~/hooks/useQuery';
describe('test the thing', () => {
let useQuerySpy;
beforeEach(() => {
// Spy on the `useQuery` function so we can optionally provide alternate behaviour.
useQuerySpy = jest.spyOn(useQueryModule, 'default');
})
afterEach(() => {
// Restore any mocked behaviour
useQuerySpy.mockRestore();
});
it('does a thing', async () => {
const refetchedApolloResponse = buildResponse(refetchData) // some function to build the shape / data of apollo response
const initialApolloResponse = buildResponse(initialData) // some function to build the shape / data of apollo response
const mockRefetch = jest.fn().mockResolvedValue({ data: refetchedApolloResponse });
useQuerySpy.mockReturnValue({ data: initialApolloResponse, refetch: mockRefetch });
// Assert stuff
}
})
This solution did not work for me and not sure whether it will work or not because it didn't work in my case.
let queryCalled = false
const testingData = (value) => ({
data: {....}
})
const TESTING_MOCK = {
request: {
query: TESTING_QUERY,
variables: { some: "variables" },
},
result: () => {
if (queryCalled) return testingData("refetched");
else {
queryCalled = true;
return testingData("first fetched");
}
},
};
So I solved it by another way which is:
const mocks = [
{
request: {
query: GET_DOG_QUERY,
variables: {
name: 'Buck',
},
},
result: {
data: {
dog: { id: '1', name: 'Buck', breed: 'bulldog' },
},
},
newData: jest.fn(() => ({
data: {
dog: { id: '1', name: 'Refetched-Buck', breed: 'refetched-bulldog' },
},
})),
},
];
It worked like a charm for me.

Mock-axios-adapter not mocking get request

I'm trying to test this function:
export const fetchCountry = (query) => {
return dispatch => {
dispatch(fetchCountryPending());
return axios.get(`${process.env.REACT_APP_API_URL}/api/v1/countries/?search=${query}`)
.then(response => {
const country = response.data;
dispatch(fetchCountryFulfilled(country));
})
.catch(err => {
dispatch(fetchCountryRejected());
dispatch({type: "ADD_ERROR", error: err});
})
}
}
Here is my test:
describe('country async actions', () => {
let store;
let mock;
beforeEach(() => {
mock = new MockAdapter(axios)
store = mockStore({ country: [], fetching: false, fetched: false })
});
afterEach(() => {
mock.restore();
store.clearActions();
});
it('dispatches FETCH_COUNTRY_FULFILLED after axios request', () => {
const query = 'Aland'
mock.onGet(`/api/v1/countries/?search=${query}`).reply(200, country)
store.dispatch(countryActions.fetchCountry(query))
.then(() => {
const actions = store.getActions();
expect(actions[0]).toEqual(countryActions.fetchCountryPending())
expect(actions[1]).toEqual(countryActions.fetchCountryFulfilled(country))
});
});
When I run this test, I get an error UnhandledPromiseRejectionWarning and that fetchCountryPending was not received and that fetchCountryRejected was. It seems as if onGet() is not doing anything. When I comment out the line
mock.onGet('/api/v1/countries/?search=${query}').reply(200, country), I end up getting the exact same result, making me believe that nothing is being mocked. What am I doing wrong?
I couldn't get the .then(() => {}) to work, so I turned the function into an async function and awaited the dispatch:
it('dispatches FETCH_COUNTRY_FULFILLED after axios request', async () => {
const query = 'Aland'
mock.onGet(`/api/v1/countries/?search=${query}`).reply(200, country)
await store.dispatch(countryActions.fetchCountry(query))
const actions = store.getActions();
expect(actions[0]).toEqual(countryActions.fetchCountryPending())
expect(actions[1]).toEqual(countryActions.fetchCountryFulfilled(country))
});

Testing fetch action in react/redux app

Im starting with unit testing and Jest. What I want is to test the action's response after fetching some resources from the db.
This is the action code:
export function loadPortlets() {
return function(dispatch) {
return portletApi.getAllPortlets().then(response => {
dispatch(loadPortletsSuccess(response));
dispatch(hideLoading());
}).catch(error => {
dispatch({ type: null, error: error });
dispatch(hideLoading());
throw(error);
});
};
}
This code is fetching data from:
static getAllPortlets() {
return fetch(`${API_HOST + API_URI}?${RES_TYPE}`)
.then(response =>
response.json().then(json => {
if (!response.ok) {
return Promise.reject(json);
}
return json;
})
);
}
And this is the test:
import configureMockStore from 'redux-mock-store';
import thunk from 'redux-thunk';
import fetch from 'isomorphic-fetch';
import fetchMock from 'fetch-mock';
import * as actions from '../portletActions';
import * as types from '../actionTypes';
const middlewares = [thunk];
const mockStore = configureMockStore(middlewares);
const mockResponse = (status, statusText, response) => {
return new window.Response(response, {
status: status,
statusText: statusText,
headers: {
'Content-type': 'application/json'
}
});
};
describe('async actions', () => {
afterEach(() => {
fetchMock.reset();
fetchMock.restore();
})
it('calls request and success actions if the fetch response was successful', () => {
window.fetch = jest.fn().mockImplementation(() =>
Promise.resolve(mockResponse(200, null, [{ portlets: ['do something'] }])));
const store = mockStore({ portlets: []});
return store.dispatch(actions.loadPortlets())
.then(() => {
const expectedActions = store.getActions();
expect(expectedActions[0]).toContain({ type: types.LOAD_PORTLETS_SUCCESS });
})
});
});
And this is the result of running the test:
FAIL src\actions\__tests__\portletActions.tests.js
● async actions › calls request and success actions if the fetch response was successful
expect(object).toContain(value)
Expected object:
{"portlets": [// here an array of objects], "type": "LOAD_PORTLETS_SUCCESS"}
To contain value:
{"type": "LOAD_PORTLETS_SUCCESS"}
at store.dispatch.then (src/actions/__tests__/portletActions.tests.js:56:34)
at <anonymous>
at process._tickCallback (internal/process/next_tick.js:188:7)
In the redux docs for this example (https://redux.js.org/recipes/writing-tests), they receive a result containing only the action types executed, but I'm getting the real data and the action inside the array.
So I'm not sure if the code is wrong, or the test, or both!
Thanks in advance, any help is highly appreciated!
You're testing too much with this unit test. I see you are using thunks it looks like so you can change your fetch to be passed as a module to the thunk and do something like this. I used jasmine but it's basically the same thing. You don't want to mock your store here just the action and dispatch. The point of the unit test should be to test the async action, not to test getting real data from the db or redux store interactions so you can stub all that out.
For reference configureStore would look like this...
const createStoreWithMiddleware = compose(
applyMiddleware(thunk.withExtraArgument({ personApi }))
)(createStore);
And the test case...
it('dispatches an action when receiving', done => {
const person = [{ firstName: 'Francois' }];
const expectedAction = {
type: ActionTypes.RECEIVED,
payload: {
people,
},
};
const dispatch = jasmine.createSpy();
const promise = Q.resolve(person);
const personApi = {
fetchPerson: jasmine
.createSpy()
.and.returnValue(promise),
};
const thunk = requestPerson();
thunk(dispatch, undefined, { personApi });
promise.then(() => {
expect(dispatch.calls.count()).toBe(2);
expect(dispatch.calls.mostRecent().args[0]).toEqual(expectedAction);
done();
});
});

Testing Observable epic which invokes other epic

I am trying to test a Redux Observable epic which dispatches an action to invoke an other epic. Somehow the invoked epic is not called.
Lets say my epics looks like this;
const getJwtEpic = (action$, store, { api }) =>
action$.ofType('GET_JWT_REQUEST')
.switchMap(() => api.getJWT()
.map(response => {
if (response.errorCode > 0) {
return {
type: 'GET_JWT_FAILURE',
error: { code: response.errorCode, message: response.errorMessage },
};
}
return {
type: 'GET_JWT_SUCCESS',
idToken: response.id_token,
};
})
);
const bootstrapEpic = (action$, store, { api }) =>
action$.ofType('BOOTSTRAP')
.switchMap(() =>
action$.filter(({ type }) => ['GET_JWT_SUCCESS', 'GET_JWT_FAILURE'].indexOf(type) !== -1)
.take(1)
.mergeMap(action => {
if (action.type === 'GET_JWT_FAILURE') {
return Observable.of({ type: 'BOOTSTRAP_FAILURE' });
}
return api.getProfileInfo()
.map(({ profile }) => ({
type: 'BOOTSTRAP_SUCCESS', profile,
}));
})
.startWith({ type: 'GET_JWT_REQUEST' })
);
When I try to test the bootstrapEpic in Jest with the following code;
const response = {};
const api = { getJWT: jest.fn() };
api.getJWT.mockReturnValue(Promise.resolve(response));
const action$ = ActionsObservable.of(actions.bootstrap());
const epic$ = epics.bootstrapEpic(action$, null, { api });
const result = await epic$.toArray().toPromise();
console.log(result);
The console.log call gives me the following output;
[ { type: 'GET_JWT_REQUEST' } ]
Somehow the getJwtEpic isn't called at all. I guess it has something to do with the action$ observable not dispatching the GET_JWT_REQUEST action but I can't figure out why. All help is so welcome!
Assuming actions.rehydrate() returns an action of type BOOTSTRAP and the gigya stuff is a typo,
getJwtEpic isn't called because you didn't call it yourself 🤡 When you test epics by manually calling them, then it's just a function which returns an Observable, without any knowledge of the middleware or anything else. The plumbing that connects getJwtEpic as part of the root epic, and provides it with (action$, store) is part of the middleware, which you're not using in your test.
This is the right approach, testing them in isolation, without redux/middleware. 👍 So you test each epic individually, by providing it actions and dependencies, then asserting on the actions it emits and the dependencies it calls.
You'll test the success path something like this:
const api = {
getProfileInfo: () => Observable.of({ profile: 'mock profile' })
};
const action$ = ActionsObservable.of(
actions.rehydrate(),
{ type: 'GET_JWT_SUCCESS', idToken: 'mock token' }
);
const epic$ = epics.bootstrapEpic(action$, null, { api });
const result = await epic$.toArray().toPromise();
expect(result).toEqual([
{ type: 'GET_JWT_REQUEST' },
{ type: 'BOOTSTRAP_SUCCESS', profile: 'mock profile' }
]);
Then you'll test the failure path in another test by doing the same thing except giving it GET_JWT_FAILURE instead of GET_JWT_SUCCESS. You can then test getJwtEpic separately as well.
btw, ofType accepts any number of types, so you can just do action$.ofType('GET_JWT_SUCCESS', 'GET_JWT_FAILURE')

Resources