Im trying to mock a fetch request to confirm if the right actions were dispatched:
the redux function:
export function loginRequest(email, password) {
return (dispatch) => {
dispatch(login(email, password))
return fetch('http://localhost:8000/login-success.json')
.then((response) => response.json())
.then((response) => dispatch(loginSuccess()))
.catch((error)=>dispatch(loginFailure()))
}
}
the test:
test("API returns the right response, the store received two actions LOGIN and LOGGING_SUCCESS", () => {
const store = mockStore({})
fetchMock.get('*', {})
return store.dispatch(loginRequest('jack', 124)).then(()=>{
const actions = store.getActions()
console.log(actions)
expect(actions).toEqual([login('jack', 124), loginSuccess()])
})
})
the console.log output:
[
{ type: 'LOGIN', user: { email: 'jack', password: 124 } },
{ type: 'LOGIN_FAILURE' }
]
im expecting the second action to be LOGIN_SUCCESS action instead. it seems like the mock isnt working at all. Am i missing something here
SOLVED: in the file where i defined the loginRequest function, i was importing fetch (import fetch from 'node-fetch';) hence calling the function in the test resulted in an actual fetch call rather than fetch-mock
Related
I have an API called getQuote and a component called QuoteCard. Inside QuoteCard I'm trying to render an array of users that liked a quote. The API works fine, I have tested it, and the code below for getting the users works fine too.
const Post = async (url, body) => {
let res = await fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json",
"accept": "*/*"
},
body: JSON.stringify(body)
}).then(r => r.json());
return res;
}
const getAllLikes = async () => {
let users = await Post('api/getQuote', {
id: "639e3aff914d4c4f65418a1b"
})
return users
}
console.log(getAllLikes())
The result is working as expected :
However, when trying to map this promise result array to render it onto the page is where I have problems. I try to render like this:
<div>
{getAllLikes().map((user) => (
<p>{user}</p>
))}
</div>
However, I get an error that states:
getAllLikes(...).map is not a function
I don't understand why this is happening. Why can't I map the array? Is it because it's a promise or something?
And if anyone needs to see the getQuote API, here it is:
//Look ma I wrote an API by myself! :D
import clientPromise from "../../lib/mongodb";
const ObjectId = require('mongodb').ObjectId;
import nc from "next-connect";
const app = nc()
app.post(async function getQuote(req, res) {
const client = await clientPromise;
const db = client.db("the-quotes-place");
try {
let quote = await db.collection('quotes').findOne({
_id: new ObjectId(req.body.id)
})
res.status(200).json(JSON.parse(JSON.stringify(quote.likes.by)));
} catch (e) {
res.status(500).json({
message: "Error getting quote",
success: false
})
console.error(e);
}
})
export default app
Thanks for any help!
It is due to the fact that getAllLikes is an async function and thus it returns promise which does not have a map function.
You can either save it in a state variable before using await Or chain it with .then.
Minimal reproducible example which works
const getAllLikes = async () => {
return ['a', 'b']
}
getAllLikes().then((r) => r.map((g) => { console.log(g) }))
Edit: The above code won't work if directly used with jsx since the return of getAllLikes will still be a promise. Solution would be to save it in a state variable and then using it.
I am from Angular and I believe we call pipe on Observables (or Promises). Map can then be called inside the pipe function
observable$ = getAllLikes().pipe(map( user => <p>{user}</p>))
If there is no pipe, I can only think of manually subscribing (which is not a good practice)
sub$ = getAllLikes().subscribe( user => <p>{user}</p>)
// unsub from sub$ appropriately
// We do this from ngOnDestroy in angular
ngOnDestroy() {
this.sub$?.unsubscribe()
}
I am using Redux Toolkit's createAsyncThunk for an API request. This specific thunk is calling a sepparate non-anonymous function as its second parameter:
export const onInfoSubmit = createAsyncThunk('info/onInfoSubmit', handlePayload)
The handlePayload function goes like:
export async function handlePayload (data, { dispatch, rejectWithValue, getState }) {
dispatch(updateInfo({ isLoadingInfoRequest: true }))
if (!isFeatureEnabled(FEATURE_NAME)) {
return handleUpdateInfo(data, { dispatch, rejectWithValue, getState })
}
return thirdPartyApiCall()
.then((response) => {
data.thirdPartyResponse = response
return handleUpdateInfo(data, { dispatch, rejectWithValue, getState })
})
}
The handleUpdateInfo function is used to set Session Storage items, send a PUT request with the dynamically created payload with an axios call and, in case of failures or in case of response errors, trigger reject accordingly.
My question is, how could I test if, for instance, calling mockStore.dispatch(onInfoSubmit) calls the handlePayload function, and further on (if handlePayload calls handleUpdateInfo, for instance)?
I have tried the following approaches:
import configureStore from 'redux-mock-store'
import thunk from 'redux-thunk'
const middlewares = [thunk]
const mockStore = configureStore(middlewares)
jest.mock('./InfoSlice.js', () => ({
...jest.requireActual('./InfoSlice'),
handlePayload: jest.fn((data) => new Promise((resolve, reject) => resolve(data))),
handleUpdateInfo: jest.fn((data) => new Promise((resolve, reject) => resolve(data)))
}))
describe('InfoSlice unit tests', () => {
beforeEach(() => {
jest.clearAllMocks()
Object.defineProperty(window, 'sessionStorage', {
value: {
getItem: jest.fn(() => null),
setItem: jest.fn(() => null),
removeItem: jest.fn(() => null)
},
writable: true
})
})
afterEach(() => {
jest.restoreAllMocks()
})
const initialState = {
loginInfo: {
userName: 'test#test.com',
email: 'test#test.com'
},
verifyEmailResponse: {},
email: {
loginInfo: {
email: 'test#test.com'
}
}
}
it('onInfoSubmit calls handlePayload', async () => {
const store = mockStore(initialState)
await store.dispatch(onEmailSubmit({
loginInfo: {
userName: 'new#test.com',
email: 'new#test.com',
}
}))
expect(handlePayload).toHaveBeenCalled()
})
But for some reason, handlePayload never gets called in this test.
Trying to check if handleUpdateInfo gets called in the same manner, also results in a broken test...
Besides, I cannot really seem to get any of the mock functions called on these tests.
Any tips on this issue will be much appreciated!
Technically, you are mocking the export with the name handlePayload - but inside that file, onInfoSubmit has still been created with the local function definition. You would probably have to split those over a file boundary to make that work.
Generally though, that test would not be testing anything besides the question if you have actually written handlePayload in that line - the test does not give you any confidence that your code is really working.
I'd recommend to mock as little as possible of your own code, but only mock things like apis (using msw for example) and window.localStorage. Then run your thunks in a real-life environment and see it your component works. We recommend doing real-life integration tests in the Redux docs as that will give you the most confidence on actual functionality, not just implementation details.
I have created a redux that is going to request an API and if the result is 200, I want to redirect the user to another page using history.
The problem is: I don't know how to trigger this change if the action is a success.
I could redirect the user in my useCase function but I can't use history.push pathName/state argument because it only works in a React component.
So this is what I have done in my React component:
const acceptProposalHandler = () => {
store.dispatch(acceptProposal(id)).then(() => {
setTimeout(() => {
if (isAccepted) { //isAccepted is false by default but is changed to true if the
//request is 200
history.push({
pathname: urls.proposal,
state: {
starterTab: formatMessage({id: 'proposalList.tabs.negotiation'}),
},
});
}
}, 3000);
});
};
Sometimes it works but other times it wont. For some reason, .then is called even if the request fails.
I'm using setTimeOut because if I don't, it will just skip the if statement because the redux hasn't updated the state with isAccepted yet.
This is my useCase function from redux:
export const acceptProposal = (id: string) => async (
dispatch: Dispatch<any>,
getState: () => RootState,
) => {
const {auth} = getState();
const data = {
proposalId: id,
};
dispatch(actions.acceptProposal());
try {
await API.put(`/propostas/change-proposal-status/`, data, {
headers: {
version: 'v1',
'Content-Type': 'application/json',
},
});
dispatch(actions.acceptProposalSuccess());
} catch (error) {
dispatch(actions.acceptProposalFailed(error));
}
};
What I'm doing wrong? I'm using Redux with thunk but I'm not familiar with it.
".then is called even if the request fails." <- this is because acceptProposal is catching the API error and not re-throwing it. If an async function does not throw an error, it will resolve (i.e. call the .then). It can re-throw the error so callers will see an error:
export const acceptProposal = (id: string) => async (
// ... other code hidden
} catch (error) {
dispatch(actions.acceptProposalFailed(error));
// ADD: re-throw the error so the caller can use `.catch` or `try/catch`
throw error;
}
};
I am testing a Vue component's method that performs a basic fetch, and updates a data value in the .finally() block of that fetch. I can confirm that my test reaches that .finally() block, but the data value is never updated.
My method is:
updateProfile () {
fetch(updateProfileEndPoint, {
method: 'POST',
body: {email: test#test.com, id: 1234, name: 'bob},
})
.catch((error) => {
this.errorField = true;
})
.finally(() => {
this.profileUpdated = true;
});
In my Jest test, I have:
const wrapper = mount(ProfileComponent, { store,
data () {
return {
profileUpdated: false,
};
},
});
global.fetch = jest.fn(() =>
Promise.resolve({
profileUpdate: 'complete',
})
);
wrapper.vm.updateProfile();
expect(wrapper.vm.profileUpdated).toBe(true);
However, profileUpdated remains false. Strangely, if I console.log(this.profileUpdate) in that method, the updated value true does log. However, my tests still receive false.
Your assertion occurs before the asynchronous fetch call actually completes.
One solution is to return the fetch result (a Promise) from updateProfile(), allowing the test to await the call:
// MyComponent.vue
export default {
methods: {
updateProfile() {
return fetch(...).catch(...).finally(...)
} 👆
}
}
// MyComponent.spec.js 👇
it('updateProfile() sets profileUpdated flag', async () => {
const wrapper = mount(...)
👇
await wrapper.vm.updateProfile()
expect(wrapper.vm.profileUpdated).toBe(true)
})
In the Writing Tests section of Redux, how does the store.dispatch(actions.fetchTodos()) not invoke the fetch method, if store.dispatch is literally calling actions.fetchTodos?
it('creates FETCH_TODOS_SUCCESS when fetching todos has been done', (done) => {
nock('http://example.com/')
.get('/todos')
.reply(200, { todos: ['do something'] })
const expectedActions = [
{ type: types.FETCH_TODOS_REQUEST },
{ type: types.FETCH_TODOS_SUCCESS, body: { todos: ['do something'] } }
]
const store = mockStore({ todos: [] }, expectedActions, done)
store.dispatch(actions.fetchTodos())
})
It is calling the fetch method, but the nock line:
nock('http://example.com/')
.get('/todos')
.reply(200, { todos: ['do something'] })
stubs out the HTTP request so that fetch simply returns the right data (without actually hitting the endpoint).
Another option is to extract the call to fetch into another module — say, api — and use it inside your action creator. In tests, you would simply mock out the appropriate methods on the api module.