Spy on mock service worker (msw)? - javascript

I'm starting to use the msw (mock service worker) after watching this example of how to use it for testing API calls in React applications.
Is there any way that we can spy on the mock service worker?
For example:
import React from 'react'
import { render, act, await } from '#testing-library/react'
import userEvent from '#testing-library/user-event'
import { rest } from 'msw'
import { setupServer } from 'msw/node'
import SearchBox from '.'
const fakeServer = setupServer(
rest.get(
'https://api.flickr.com/services/rest/?method=flickr.photos.search',
(req, res, ctx) => res(ctx.status(200), ctx.json({ data: { photos: { photo: [] },},}))
)
)
beforeAll(() => {fakeServer.listen()})
afterEach(() => {fakeServer.resetHandlers()})
afterAll(() => fakeServer.close())
test('it calls Flickr REST request when submitting search term', async () => {
const { getByLabelText } = render(<SearchBox />)
const input = getByLabelText('Search Flickr')
const submitButton = getByLabelText('Submit search')
await act(async () => {
await userEvent.type(input,'Finding Wally')
await userEvent.click(submitButton)
})
await wait()
// TODO: assert that the fakeServer was called once and with the correct URL
})
The component to test looks like this:
import React, { useState } from 'react'
import axios from 'axios'
import './index.css'
function SearchBox({ setPhotos }) {
const [searchTerm, setSearchTerm] = useState('')
const handleTyping = (event) => {
event.preventDefault()
setSearchTerm(event.currentTarget.value)
}
const handleSubmit = async (event) => {
event.preventDefault()
try {
const restURL = `https://api.flickr.com/services/rest/?method=flickr.photos.search&api_key=${
process.env.REACT_APP_API_KEY
}&per_page=10&format=json&nojsoncallback=1'&text=${encodeURIComponent(
searchTerm
)}`
const { data } = await axios.get(restURL)
const fetchedPhotos = data.photos.photo
setPhotos(fetchedPhotos)
} catch (error) {
console.error(error)
}
}
return (
<section style={styles.container}>
<form action="" method="" style={styles.form}>
<input
aria-label="Search Flickr"
style={styles.input}
value={searchTerm}
onChange={handleTyping}
/>
<button
aria-label="Submit search"
style={styles.button}
onClick={handleSubmit}
>
SEARCH
</button>
</form>
</section>
)
}
I have got a working test, but I feel it leans towards an implementation test since it uses a spy on the setPhotos
test('it calls Flickr REST request when submitting search term', async () => {
const fakeSetPhotos = jest.fn(() => {})
const { getByLabelText } = render(<SearchBox setPhotos={fakeSetPhotos} />)
const input = getByLabelText('Search Flickr')
const submitButton = getByLabelText('Submit search')
await act(async () => {
await userEvent.type(input, 'Finding Walley')
await userEvent.click(submitButton)
})
await wait()
expect(fakeSetPhotos).toHaveBeenCalledWith([1, 2, 3])
})

The devs at mswjs are really nice and helpful. They took their time to advice me on how to approach it.
TLDR;
The current working test I got is fine - just suggested an alternative to jest.fn() - I do like the readability of the their suggestion:
test('...', async () => {
let photos
// Create an actual callback function
function setPhotos(data) {
// which does an action of propagating given data
// to the `photos` variable.
photos = data
}
// Pass that callback function as a value to the `setPhotos` prop
const { getByLabelText } = render(<SearchBox setPhotos={setPhotos} />)
// Perform actions:
// click buttons, submit forms
// Assert result
expect(photos).toEqual([1, 2, 3])
})
Another thing I wanted to test was that it actually calls a valid REST URL.
You can reflect an invalid query parameter in the response resolver.
If the query parameter is missing/invalid your real server would not
produce the expected data, right? So with MSW your "real server" is
your response resolver. Check the presence or value of that query
parameter and raise an error in case that parameter is invalid.
rest.get('https://api.flickr.com/services/rest/?method=flickr.photos.search',
(req, res, ctx) => { const method = req.url.searchParams.get('method')
if (!method) {
// Consider a missing `method` query parameter as a bad request.
return res(ctx.status(400)) }
// Depending on your logic, you can also check if the value of the `method` // parameter equals to "flickr.photos.search".
return res(ctx.json({ successful: 'response' })) })
Now, if your app misses the method query parameter in the request URL, it would get a 400 response, and shouldn't call the setPhotos callback in case of such unsuccessful response.

If you want to avoid mocking you could spy on axios.get and assert that it was called correctly.
test('it calls Flickr REST request when submitting search term', async () => {
const getSpy = jest.spyOn(axios, 'get');
const { getByLabelText } = render(<SearchBox />)
const input = getByLabelText('Search Flickr')
const submitButton = getByLabelText('Submit search')
await act(async () => {
await userEvent.type(input,'Finding Wally')
await userEvent.click(submitButton)
})
await wait()
expect(getSpy).toHaveBeenCalledTimes(1)
})

Related

React router dom redirect function is not redirecting

I have a fetch function in my useEffect that gets some data from the backend, but it sends a JWT token to check for user authorization to retrieve the data.
In case the user is not authorized I want to redirect it to my unauthorized page, but the redirect function just does not work at all, it does absolutely nothing at all.
This is what my useEffect looks like:
useEffect(() => {
const populateData = async () => {
const response = await OS_Service.getAll('user.token');
if (!response) return redirect('/unauthorized')
setAllServiceOrders(response.data);
setFetching(false);
};
populateData();
}, [setAllServiceOrders, user]);
Anyone knows why it is not working?
import { useHistory } from 'react-router-dom
const Comp = () =>{
const history = useHistory()
......
useEffect(() => {
const populateData = async () => {
const response = await OS_Service.getAll('user.token');
if(!response) history.push('/unauthorized')
setAllServiceOrders(response.data);
setFetching(false);
};
populateData();
}, [setAllServiceOrders, user]);
........
return (<>......</>)
}
The redirect utility function is for use only in Route loaders and actions. In React components you should use the useNavigate hook.
Example:
const navigate = useNavigate();
useEffect(() => {
const populateData = async () => {
const response = await OS_Service.getAll('user.token');
if (!response) {
navigate('/unauthorized', { replace: true });
return;
}
setAllServiceOrders(response.data);
setFetching(false);
};
populateData();
}, [setAllServiceOrders, user]);

useEffect must not return anything beside a function, which is used for clean-up Error Comes up Every Screen [duplicate]

I was trying the useEffect example something like below:
useEffect(async () => {
try {
const response = await fetch(`https://www.reddit.com/r/${subreddit}.json`);
const json = await response.json();
setPosts(json.data.children.map(it => it.data));
} catch (e) {
console.error(e);
}
}, []);
and I get this warning in my console. But the cleanup is optional for async calls I think. I am not sure why I get this warning. Linking sandbox for examples. https://codesandbox.io/s/24rj871r0p
For React version <=17
I suggest to look at Dan Abramov (one of the React core maintainers) answer here:
I think you're making it more complicated than it needs to be.
function Example() {
const [data, dataSet] = useState<any>(null)
useEffect(() => {
async function fetchMyAPI() {
let response = await fetch('api/data')
response = await response.json()
dataSet(response)
}
fetchMyAPI()
}, [])
return <div>{JSON.stringify(data)}</div>
}
Longer term we'll discourage this pattern because it encourages race conditions. Such as — anything could happen between your call starts and ends, and you could have gotten new props. Instead, we'll recommend Suspense for data fetching which will look more like
const response = MyAPIResource.read();
and no effects. But in the meantime you can move the async stuff to a separate function and call it.
You can read more about experimental suspense here.
If you want to use functions outside with eslint.
function OutsideUsageExample({ userId }) {
const [data, dataSet] = useState<any>(null)
const fetchMyAPI = useCallback(async () => {
let response = await fetch('api/data/' + userId)
response = await response.json()
dataSet(response)
}, [userId]) // if userId changes, useEffect will run again
useEffect(() => {
fetchMyAPI()
}, [fetchMyAPI])
return (
<div>
<div>data: {JSON.stringify(data)}</div>
<div>
<button onClick={fetchMyAPI}>manual fetch</button>
</div>
</div>
)
}
For React version >=18
Starting with React 18 you can also use Suspense, but it's not yet recommended if you are not using frameworks that correctly implement it:
In React 18, you can start using Suspense for data fetching in opinionated frameworks like Relay, Next.js, Hydrogen, or Remix. Ad hoc data fetching with Suspense is technically possible, but still not recommended as a general strategy.
If not part of the framework, you can try some libs that implement it like swr.
Oversimplified example of how suspense works. You need to throw a promise for Suspense to catch it, show fallback component first and render Main component when promise it's resolved.
let fullfilled = false;
let promise;
const fetchData = () => {
if (!fullfilled) {
if (!promise) {
promise = new Promise(async (resolve) => {
const res = await fetch('api/data')
const data = await res.json()
fullfilled = true
resolve(data)
});
}
throw promise
}
};
const Main = () => {
fetchData();
return <div>Loaded</div>;
};
const App = () => (
<Suspense fallback={"Loading..."}>
<Main />
</Suspense>
);
When you use an async function like
async () => {
try {
const response = await fetch(`https://www.reddit.com/r/${subreddit}.json`);
const json = await response.json();
setPosts(json.data.children.map(it => it.data));
} catch (e) {
console.error(e);
}
}
it returns a promise and useEffect doesn't expect the callback function to return Promise, rather it expects that nothing is returned or a function is returned.
As a workaround for the warning you can use a self invoking async function.
useEffect(() => {
(async function() {
try {
const response = await fetch(
`https://www.reddit.com/r/${subreddit}.json`
);
const json = await response.json();
setPosts(json.data.children.map(it => it.data));
} catch (e) {
console.error(e);
}
})();
}, []);
or to make it more cleaner you could define a function and then call it
useEffect(() => {
async function fetchData() {
try {
const response = await fetch(
`https://www.reddit.com/r/${subreddit}.json`
);
const json = await response.json();
setPosts(json.data.children.map(it => it.data));
} catch (e) {
console.error(e);
}
};
fetchData();
}, []);
the second solution will make it easier to read and will help you write code to cancel previous requests if a new one is fired or save the latest request response in state
Working codesandbox
Until React provides a better way, you can create a helper, useEffectAsync.js:
import { useEffect } from 'react';
export default function useEffectAsync(effect, inputs) {
useEffect(() => {
effect();
}, inputs);
}
Now you can pass an async function:
useEffectAsync(async () => {
const items = await fetchSomeItems();
console.log(items);
}, []);
Update
If you choose this approach, note that it's bad form. I resort to this when I know it's safe, but it's always bad form and haphazard.
Suspense for Data Fetching, which is still experimental, will solve some of the cases.
In other cases, you can model the async results as events so that you can add or remove a listener based on the component life cycle.
Or you can model the async results as an Observable so that you can subscribe and unsubscribe based on the component life cycle.
You can also use IIFE format as well to keep things short
function Example() {
const [data, dataSet] = useState<any>(null)
useEffect(() => {
(async () => {
let response = await fetch('api/data')
response = await response.json()
dataSet(response);
})();
}, [])
return <div>{JSON.stringify(data)}</div>
}
void operator could be used here.
Instead of:
React.useEffect(() => {
async function fetchData() {
}
fetchData();
}, []);
or
React.useEffect(() => {
(async function fetchData() {
})()
}, []);
you could write:
React.useEffect(() => {
void async function fetchData() {
}();
}, []);
It is a little bit cleaner and prettier.
Async effects could cause memory leaks so it is important to perform cleanup on component unmount. In case of fetch this could look like this:
function App() {
const [ data, setData ] = React.useState([]);
React.useEffect(() => {
const abortController = new AbortController();
void async function fetchData() {
try {
const url = 'https://jsonplaceholder.typicode.com/todos/1';
const response = await fetch(url, { signal: abortController.signal });
setData(await response.json());
} catch (error) {
console.log('error', error);
}
}();
return () => {
abortController.abort(); // cancel pending fetch request on component unmount
};
}, []);
return <pre>{JSON.stringify(data, null, 2)}</pre>;
}
I read through this question, and feel the best way to implement useEffect is not mentioned in the answers.
Let's say you have a network call, and would like to do something once you have the response.
For the sake of simplicity, let's store the network response in a state variable.
One might want to use action/reducer to update the store with the network response.
const [data, setData] = useState(null);
/* This would be called on initial page load */
useEffect(()=>{
fetch(`https://www.reddit.com/r/${subreddit}.json`)
.then(data => {
setData(data);
})
.catch(err => {
/* perform error handling if desired */
});
}, [])
/* This would be called when store/state data is updated */
useEffect(()=>{
if (data) {
setPosts(data.children.map(it => {
/* do what you want */
}));
}
}, [data]);
Reference => https://reactjs.org/docs/hooks-effect.html#tip-optimizing-performance-by-skipping-effects
For other readers, the error can come from the fact that there is no brackets wrapping the async function:
Considering the async function initData
async function initData() {
}
This code will lead to your error:
useEffect(() => initData(), []);
But this one, won't:
useEffect(() => { initData(); }, []);
(Notice the brackets around initData()
For fetching from an external API using React Hooks, you should call a function that fetches from the API inside of the useEffect hook.
Like this:
async function fetchData() {
const res = await fetch("https://swapi.co/api/planets/4/");
res
.json()
.then(res => setPosts(res))
.catch(err => setErrors(err));
}
useEffect(() => {
fetchData();
}, []);
I strongly recommend that you do not define your query inside the useEffect Hook, because it will be re-render infinite times. And since you cannot make the useEffect async, you can make the function inside of it to be async.
In the example shown above, the API call is in another separated async function so it makes sure that the call is async and that it only happens once. Also, the useEffect's dependency array (the []) is empty, which means that it will behave just like the componentDidMount from React Class Components, it will only be executed once when the component is mounted.
For the loading text, you can use React's conditional rendering to validate if your posts are null, if they are, render a loading text, else, show the posts. The else will be true when you finish fetching data from the API and the posts are not null.
{posts === null ? <p> Loading... </p>
: posts.map((post) => (
<Link key={post._id} to={`/blog/${post.slug.current}`}>
<img src={post.mainImage.asset.url} alt={post.mainImage.alt} />
<h2>{post.title}</h2>
</Link>
))}
I see you already are using conditional rendering so I recommend you dive more into it, especially for validating if an object is null or not!
I recommend you read the following articles in case you need more information about consuming an API using Hooks.
https://betterprogramming.pub/how-to-fetch-data-from-an-api-with-react-hooks-9e7202b8afcd
https://reactjs.org/docs/conditional-rendering.html
try
const MyFunctionnalComponent: React.FC = props => {
useEffect(() => {
// Using an IIFE
(async function anyNameFunction() {
await loadContent();
})();
}, []);
return <div></div>;
};
Other answers have been given by many examples and are clearly explained, so I will explain them from the point of view of TypeScript type definition.
The useEffect hook TypeScript signature:
function useEffect(effect: EffectCallback, deps?: DependencyList): void;
The type of effect:
// NOTE: callbacks are _only_ allowed to return either void, or a destructor.
type EffectCallback = () => (void | Destructor);
// Destructors are only allowed to return void.
type Destructor = () => void | { [UNDEFINED_VOID_ONLY]: never };
Now we should know why effect can't be an async function.
useEffect(async () => {
//...
}, [])
The async function will return a JS promise with an implicit undefined value. This is not the expectation of useEffect.
Please try this
useEffect(() => {
(async () => {
const products = await api.index()
setFilteredProducts(products)
setProducts(products)
})()
}, [])
To do it properly and avoid errors: "Warning: Can't perform a React state update on an unmounted..."
useEffect(() => {
let mounted = true;
(async () => {
try {
const response = await fetch(`https://www.reddit.com/r/${subreddit}.json`);
const json = await response.json();
const newPosts = json.data.children.map(it => it.data);
if (mounted) {
setPosts(newPosts);
}
} catch (e) {
console.error(e);
}
})();
return () => {
mounted = false;
};
}, []);
OR External functions and using an object
useEffect(() => {
let status = { mounted: true };
query(status);
return () => {
status.mounted = false;
};
}, []);
const query = async (status: { mounted: boolean }) => {
try {
const response = await fetch(`https://www.reddit.com/r/${subreddit}.json`);
const json = await response.json();
const newPosts = json.data.children.map(it => it.data);
if (status.mounted) {
setPosts(newPosts);
}
} catch (e) {
console.error(e);
}
};
OR AbortController
useEffect(() => {
const abortController = new AbortController();
(async () => {
try {
const response = await fetch(`https://www.reddit.com/r/${subreddit}.json`, { signal: abortController.signal });
const json = await response.json();
const newPosts = json.data.children.map(it => it.data);
setPosts(newPosts);
} catch (e) {
if(!abortController.signal.aborted){
console.error(e);
}
}
})();
return () => {
abortController.abort();
};
}, []);
I know it is late but just I had the same problem and I wanted to share that I solved it with a function like this!
useEffect(() => {
(async () => {
try {
const response = await fetch(`https://www.reddit.com/r/${subreddit}.json`);
const json = await response.json();
setPosts(json.data.children.map(it => it.data));
} catch (e) {
console.error(e);
}
}) ()
}, [])
With useAsyncEffect hook provided by a custom library, safely execution of async code and making requests inside effects become trivially since it makes your code auto-cancellable (this is just one thing from the feature list). Check out the Live Demo with JSON fetching
import React from "react";
import { useAsyncEffect } from "use-async-effect2";
import cpFetch from "cp-fetch";
/*
Notice: the related network request will also be aborted
Checkout your network console
*/
function TestComponent(props) {
const [cancel, done, result, err] = useAsyncEffect(
function* () {
const response = yield cpFetch(props.url).timeout(props.timeout);
return yield response.json();
},
{ states: true, deps: [props.url] }
);
return (
<div className="component">
<div className="caption">useAsyncEffect demo:</div>
<div>
{done ? (err ? err.toString() : JSON.stringify(result)) : "loading..."}
</div>
<button className="btn btn-warning" onClick={cancel} disabled={done}>
Cancel async effect
</button>
</div>
);
}
export default TestComponent;
The same demo using axios
Just a note about HOW AWESOME the purescript language handles this problem of stale effects with Aff monad
WITHOUT PURESCRIPT
you have to use AbortController
function App() {
const [ data, setData ] = React.useState([]);
React.useEffect(() => {
const abortController = new AbortController();
void async function fetchData() {
try {
const url = 'https://jsonplaceholder.typicode.com/todos/1';
const response = await fetch(url, { signal: abortController.signal });
setData(await response.json());
} catch (error) {
console.log('error', error);
}
}();
return () => {
abortController.abort(); // cancel pending fetch request on component unmount
};
}, []);
return <pre>{JSON.stringify(data, null, 2)}</pre>;
}
or stale (from NoahZinsmeister/web3-react example)
function Balance() {
const { account, library, chainId } = useWeb3React()
const [balance, setBalance] = React.useState()
React.useEffect((): any => {
if (!!account && !!library) {
let stale = false
library
.getBalance(account)
.then((balance: any) => {
if (!stale) {
setBalance(balance)
}
})
.catch(() => {
if (!stale) {
setBalance(null)
}
})
return () => { // NOTE: will be called every time deps changes
stale = true
setBalance(undefined)
}
}
}, [account, library, chainId]) // ensures refresh if referential identity of library doesn't change across chainIds
...
WITH PURESCRIPT
check how useAff kills it's Aff in the cleanup function
the Aff is implemented as a state machine (without promises)
but what is relevant to us here is that:
the Aff encodes how to stop the Aff - You can put your AbortController here
it will STOP running Effects (not tested) and Affs (it will not run then from the second example, so it will NOT setBalance(balance)) IF the error was thrown TO the fiber OR INSIDE the fiber
Ignore the warning, and use the useEffect hook with an async function like this:
import { useEffect, useState } from "react";
function MyComponent({ objId }) {
const [data, setData] = useState();
useEffect(() => {
if (objId === null || objId === undefined) {
return;
}
async function retrieveObjectData() {
const response = await fetch(`path/to/api/objects/${objId}/`);
const jsonData = response.json();
setData(jsonData);
}
retrieveObjectData();
}, [objId]);
if (objId === null || objId === undefined) {
return (<span>Object ID needs to be set</span>);
}
if (data) {
return (<span>Object ID is {objId}, data is {data}</span>);
}
return (<span>Loading...</span>);
}
The most easy way is to use useAsyncEffect from 'use-async-effect'
You can find it on NPM.
const ProtectedRoute = ({ children }) => {
const [isAuth, setIsAuth] = useState(false);
useAsyncEffect(async () => {
try {
const data = await axios("auth");
console.log(data);
setIsAuth(true);
} catch (error) {
console.log(error);
}
}, []);
if (!isAuth)
return <Navigate to="/signin" />
return children;
}

How to wait for axios function to return value when in service class

I want to structure my application with some sort of service class, but whenever I extract my axios calls from the page, then the axios function seems to return "undefined".
My page looks like this. The signin function is called when the user hits the button. When I put the axios call in the page like this, everything works fine. The usestate is updated and displays.
export default function AccountPage() {
const [signinResponse, setSigninResponse] = useState();
async function signin() {
await axios
.get(
`...url...`
)
.then((res) => {
setSigninResponse(res)
});
}
...
However, when I take the axios function and move it to a service class like this
import axios from "axios";
export async function tableauSignin() {
await axios
.get(
`...url...`
)
.then((res) => {
console.log(res);
return res;
});
}
and then import and make the call like this
import { tableauSignin } from "../services/tableau-online.service";
...
export default function AccountPage() {
const [signinResponse, setSigninResponse] = useState();
async function signin() {
const r = await tableauSignin();
setSigninResponse(r);
console.log(r);
}
...
the log from the service class is good but the log on the account page is undefined.
As #RobinZigmond mentioned in comment. The following solution will work but it's a needless.
it's a needless verbose way of just doing export function
tableauSignin() { return axios.get(url).then(response =>
response.data) }.
export async function tableauSignin() {
return await axios.get(url).then(response => response.data)
}
This Solution may be more useful
const getData = async () => {
let res = await axios.get("url");
let { data } = res.data; //or res
return data;
};
You can also import this way
var response = await getData();

React `act` warning on DidMount and Promise

Okay so I have this bunch of code that's is thrown on useEffect(() => {...}, []) a.k.a componentDidMount.
// utils/apiCalls.ts
export const loadData = async <T>(
url: string,
errorMsg = "Couldn't retrieve data.",
): Promise<T> => {
const res = await fetch(url, { mode: 'cors', credentials: 'include' });
if (res.ok) return await res.json();
throw new Error(errorMsg);
};
export const loadChat = (id: number): Promise<IChat> => {
return loadData<IChat>(
`${CHAT_API}/${id}/nested/`,
"We couldn't get the chat.",
);
};
// components/MessageContainer.tsx
const MessageContainer = (/* props */) => {
/*
* Some coding...
*/
useEffect(() => {
if (session === null) return;
if (chat === null) {
loadChat(session.chat).then(setChat).catch(alert);
return;
}
// More coding...
}, [session, chat]);
};
The problem comes when I try to test it with #testing-library/react since it gives me this warning Warning: An update to MessagesContainer inside a test was not wrapped in act(...).
How can I make a correct test for this?
Here's the test I have right now.
// tests/MessagesContainer.spec.tsx
describe('MessagesContainer suite', () => {
it('loads messages on mount', () => {
fetchMock.mockResponseOnce(JSON.stringify(ChatMock));
render(
<SessionContext.Provider value={SessionMock}>
<MessagesContainer {...MessagesContainerMockedProps} />
</SessionContext.Provider>,
);
expect(fetchMock.mock.calls.length).toEqual(1);
});
});
NOTE: Wrapping render on act did not work.
fetch is async function which finished only all regular script execution ends. You need to really await fetch finished before call expect.
Moreover, it is not recommend to use testing-library as you did. You want to check how element rendered after fetch, write you test accordance to exception result in UI.
For instance, if after fetching you expect something like this:
<span>message</span>
you expect span with message, and test will be:
expect(screen.findByText('message'));
So at the end I used waitFor in order to check if element has been mounted.
// tests/ChatMessagesContainer.spec.tsx
describe('ChatMessagesContainer suite', () => {
it('renders multiple messages', async () => {
const messageMock = Object.assign({}, MessageMock);
messageMock.message = faker.lorem.words();
const chatMock = Object.assign({}, ChatMock);
chatMock.chat_message_set = [MessageMock, messageMock];
fetchMock.mockResponseOnce(JSON.stringify(chatMock));
const { getAllByText } = render(
<AuthContext.Provider value={UserMock}>
<SessionContext.Provider value={SessionMock}>
<MessagesContainer {...MessagesContainerMockedProps} />
</SessionContext.Provider>
</AuthContext.Provider>,
);
// THIS IS THE IMPORTANT PART
expect(await screen.findByText(MessageMock.message)).toBeInTheDocument();
expect(getAllByText(MessageMock.author.username).length).toEqual(2);
});
});
This article was really useful Maybe you don't need act.

Getting an Async action creator test to pass Redux

I have an async actionCreator which sends off an API call - on success it gives the reponse on failure - it fails. I am writing tests for the call but I can't get the test to send back the correct response.
Here is the function I am testing - Each dispatch dispatches an action.
const getStudyData = () => {
return async dispatch => {
try {
dispatch(fetchStudiesBegin());
const res = await DataService.fetchAllStudyData();
dispatch(fetchStudiesSuccess(res))
}
catch (err) {
dispatch(fetchStudiesError(err))
}
}
}
const fetchStudiesBegin = () => ({
type: types.FETCH_STUDIES_BEGIN
});
const fetchStudiesSuccess = studies => ({
type: types.FETCH_STUDIES_SUCCESS,
payload: { studies }
});
const fetchStudiesError = error => ({
type: types.FETCH_STUDIES_ERROR,
payload: { error }
});
This is the test that I have written - It is however giving me the ERROR response instead of the SUCCESS response
import configureStore from 'redux-mock-store';
const middlewares = [ thunk ];
const mockStore = configureStore(middlewares);
import fetchMock from 'fetch-mock';
describe('Test thunk action creator for the API call for studies ', () => {
it('expected actions should be dispatched on successful request', () => {
const store = mockStore({});
const expectedActions = [
types.FETCH_STUDIES_BEGIN,
types.FETCH_STUDIES_SUCCESS
];
// Mock the fetch() global to always return the same value for GET
// requests to all URLs.
fetchMock.get('*', { response: 200 });
return store.dispatch(dashboardOperations.getStudyData())
.then(() => {
const actualActions = store.getActions().map(action => action.type);
expect(actualActions).toEqual(expectedActions);
});
fetchMock.restore();
});
});
});

Categories