How to test rxjs ajax call with jest? - javascript

I have a container component where I'm fetching the data via ajax operator from rxjs
const data = ajax(someUrl).pipe(map(r => r.response));
And in my componentDidMount I have
data.subscribe((data) => {
this.setState({ data });
});
// test.js
import React from 'react';
import { mount } from 'enzyme';
import { ajax } from 'rxjs/ajax'
import App from '../src/App';
describe('<App />', () => {
const wrap = mount(<App />);
const data = [{ 1: 'a' }];
const mock = ajax('http://url.com').pipe(map(() => data));
it('renders', () => {
console.log(mock.subscribe(x => x));
expect(wrap.find(App).exists()).toBe(true);
});
});
How do I go about mocking the response so that when I run the test it I can pass that data on to other components and check if they render?
All the testing examples I've found have been redux-Observable ones which I'm not using.
Thanks a lot!

First you need to understand that you should be testing one thing at a time.
Meaning that testing your async method execution should be separated from testing your components rendering proper content.
To test async methods you can mock your data and than mock timers in Jest.
https://jestjs.io/docs/en/tutorial-async
https://jestjs.io/docs/en/asynchronous
https://jestjs.io/docs/en/timer-mocks.html
with jest.useFakeTimers() and techniques mentioned above.
For testing component proper rendering use jest snapshots and e2e testing (can be done with ex. TestCafe)
To connect those approaches you need to design you app in a way that will allow you to:
The API you call in your component, should be external to component and be called from that external source (different file, different class, however you design it), so you can replace it in test.
Whole API should be modular, so you can take one module and test it without initializing whole API just for this case.
If you design your app in such manner, you can initialize part of the API with mock data, than render your component in test and as it will call mocked API, you can check if it renders what you expect it to.

Related

Unit test redux toolkit queries connected via fixedCacheQuery using react testing library

I have two components in my react app:
Component A
Performs a lazy fetch of users. This looks like:
const ComponentA = () => {
const [trigger, {data}] = useLazyLoadUsers({
fixedCacheKey: fixedLoadUsersKey,
});
useEffect(() => {
trigger();
}, []);
return <div>{data.map(user => user.id)}</div>
}
Component B
Wants to render a loading indicator while useLazyLoadUsers's isLoading property equals true. This component looks like this:
const ComponentB = () => {
const [, {isLoading}] = useLazyLoadUsers({
fixedCacheKey: fixedLoadUsersKey,
});
if (!isLoading) {
return <div>Users loaded</div>
}
return <div>Loading users</div>
}
The issue
While this works well (the states are in sync via the fixedLoadUsersKey), I'm struggling to find documentation or examples on how to test Component B.
Testing Component A is well documented here https://redux.js.org/usage/writing-tests#ui-and-network-testing-tools.
I already have an overwritten react testing library render method that provides a real store (which includes all my auto-generated queries).
What I would like to do is testing that Component B loading indicator renders - or not - based on a mocked isLoading value. I want to keep my current or similar implementation, not duplicating the isLoading state into another slice.
So far, I have tried mocking useLazyLoadUsers without success. I also tried dispatching an initiator before rendering the test, something like
it('should render the loading indicator', async () => {
const store = makeMockedStore();
store.dispatch(myApi.endpoints.loadUsers.initiate());
render(<ComponentB />, {store});
expect(await screen.findByText('Loading users')).toBeVisible();
})
This didn't work either.
Does someone have a hint on how to proceed here or suggestions on best practices?

How do I pass provider used in hook to Jest/Enzyme tests?

I have several components where I added a analyticsProvider to track usage. In each one of these, I'm using an useEffect hook and importing useAnalytics:
import { useAnalytics } from '#framework/component-analytics';
const { trackView } = useAnalytics();
useEffect(() => {
trackView(`/liveTraffic`, 'liveTrafficPage');
}, [trackView]);
I also have a AnalyticsProvider at the root of the application that passes all the necessary config to the child consumers (ie. useAnalytics).
I'm running into an issue when running tests: 'useAnalytics' must be called from inside an '<AnalyticsProvider>', or must be provided with an 'eventManagerConfiguration' argument.
What is the best way to pass the component needed and related configuration in my tests? I'm fairly new to React and struggling to figure out what to use. I've looked into wrapping the components but it's not working.
describe('has no liveTraffic list items', () => {
const component = shallow(analyticsWrapper(<LiveTrafficPage {...props} />));
it('should NOT render LiveTrafficListing', () => {
expect(component.update().find(LiveTrafficListing).length).toEqual(0);
});
});

How to render a conditional component in a Test file? React Testing Library

I have a React component that doesn't render until fetchUser status is not loading. My test fails because the component is not rendered the first time and therefore can not find page loaded text. How can I mock my const [fetchStatusLoading, setFetchStatusLoading] = useState(false) on my test so that the page would get rendered instead of loading text?
const fetchUser = useSelector((state) => (state.fetchUser));
const [fetchStatusLoading, setFetchStatusLoading] = useState(true);
useEffect(() => {
setFetchStatusLoading(fetchUser .status === 'loading');
}, [fetchStatusLoading, fetch.status]);
useEffect(() => {
// this is a redux thunk dispatch that sets fetch.status to loading/succeeded
dispatch(fetchAPI({ username }));
}, []);
if(fetchStatusLoading) return 'loading..';
return (<>page loaded</>)
// test case fails
expect(getByText('page loaded')).toBeInTheDocument();
If you were able to mock the loading state you would only be testing the code on the "render" part and not all the component logic you are supposed to test, as this is the way Testing Library means to shine.
If this is just an asynchronous problem, you could do something like:
test('test page loaded', async () => {
render(<MyComponent />);
expect(await screen.findByText('page loaded')).toBeInTheDocument();
});
But it seems that your component contains an API request, so if the component accepts a function you could mock it and return the value, or you could mock fetch. Here are some reasons why you should not do that.
Using nock or mock-service-worker you can have a fake server that responds to your API requests and therefore run your component test without having to mock any internal state.
Testing Library just renders the component in a browser-like environment and does not provide an API to modify any of the props or the state. It was created with this purpose, as opposite of Enzyme, which provides an API to access the component props and state.
Check out this similar question: check state of a component using react testing library

How to test a React component that dispatches a Redux / Thunk action

I'm writing an integration test for a component that should redirect to a specific path depending on the response from an asynchronous (thunk) redux action.
This is a simplified version of my component:
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
redirect: false
}
this.props.dispatch(asyncThunkAction())
.then( () => this.setState({redirec: true}) )
.catch( (err) => console.log('action failed') )
}
...
render() {
if (this.state.redirect) {
return <Redirect to='/whocares' />
}
...
}
}
function mapStateToProps(state) {
return {
...
};
}
export default connect(mapStateToProps)(MyComponent);
I want to write a test that asserts that the component redirected to the expected path.
I am using this technique for inspecting the actual redirection path (It's not perfect but it's not the focus of this question).
The place where I am stuck is the state change in the .then() following the redux/thunk action. Because it's a promise, the redirect happens after my expect statement, so I have not been able to test that.
Here's what my test looks like:
const middlewares = [thunk];
const mockStore = configureStore(middlewares);
test('redirects after thunk action', () => {
const redirectUrl = '/whocares'
const data = {};
jest.mock('../actions');
act(() => {
ReactDOM.render(
<TestRouter
ComponentWithRedirection={<MyComponent store={mockStore(data)} />}
RedirectUrl={redirectUrl}
/>,
container);
});
expect(container.innerHTML).toEqual(
expect.stringContaining(redirectUrl)
)
})
My TestRouter just prints the anticipated redirect URL into the DOM. (Check out the link above for a full explanation of this hack.) So right now instead of hitting the expected route, my test (correctly) identifies the loading screen that appears while the thunk action is in progress.
I think the right way to do this is to mock the response from asyncThunkAction so that it returns a resolved promise with matching data, but so far I have not been able to figure out how to do that. I followed the Jest documentation on manual mocks and created the corresponding mock file:
// __mocks__/actions.js
const asyncThunkAction = function(){
return Promise.resolve({foo: 'bar'});
};
export { asyncThunkAction };
...but my test still "sees" the loading state. I don't even think it's looking at my mocked file/action.
What is the right way to do this?
Here's my "recipe" for how I was able to get this working...
Use testing-library/react...
import { render, fireEvent, waitForElement, act } from '#testing-library/react';
(+1 to #tmahle for this suggestion)
Mock axios (or in my case the API module that wraps it) by creating a "manual mock" which basically entails creating a __mocks__ directory next to the real file containing a file by the same name. Then export an object with a property that replaces the get method (or whichever one your code uses).
//__mocks__/myclient.js
export default {
get: jest.fn(() => Promise.resolve({ data: {} }))
};
Even if you don't call the mocked code in your test, you need to import it in the test file...
import myapi from '../api/myapi';
jest.mock('../api/myai');
You can mock the response from the mocked API call like this:
myapi.get.mockResolvedValueOnce({
data: { foo: "bar" },
});
I'm a little fuzzy on this part...
Even though the mocked API request responds immediately with a resolved promise, you probably need to wait for it to write expects
const { getByText, getByTestId, container } = render(<MyComponent />);
await wait(() => getByText('Some text that appears after the '));
expect(container.innerHTML).toEqual('whatever');
All of this was "out there" in various docs and SO questions... but it took me a long time to cobble it all together. Hopefully this saves you time.
This is a little bit of a sideways answer to your question, admittedly, but I would recommend trying out testing-library and the ideals that it embodies, especially for integration tests.
It is available in both DOM and React flavors, which one to use likely depends on what level of abstraction your redirect is happening at:
https://github.com/testing-library/dom-testing-library
https://github.com/testing-library/react-testing-library
With this paradigm you would not try to assert that the user gets redirected to the correct path, but rather that the correct thing is on the screen after they are redirected. You would also limit your mocking to the absolutely bare necessities (likely nothing or only browser API's that your test environment cannot emulate if you are doing a true integration test).
The overall approach here would probably have you mocking out much less and perhaps rendering a larger portion of the app. A likely-helpful example to draw from can be found here: https://codesandbox.io/s/github/kentcdodds/react-testing-library-examples/tree/master/?fontsize=14&module=%2Fsrc%2F__tests__%2Freact-router.js&previewwindow=tests
Because there's less mocking in this approach, the specifics for how you can accomplish this would likely come from outside the scope of the example you've given, but the above example link should help a lot with getting started.

How to mock an asynchronous function call in another class

I have the following (simplified) React component.
class SalesView extends Component<{}, State> {
state: State = {
salesData: null
};
componentDidMount() {
this.fetchSalesData();
}
render() {
if (this.state.salesData) {
return <SalesChart salesData={this.state.salesData} />;
} else {
return <p>Loading</p>;
}
}
async fetchSalesData() {
let data = await new SalesService().fetchSalesData();
this.setState({ salesData: data });
}
}
When mounting, I fetch data from an API, which I have abstracted away in a class called SalesService. This class I want to mock, and for the method fetchSalesData I want to specify the return data (in a promise).
This is more or less how I want my test case to look like:
predefine test data
import SalesView
mock SalesService
setup mockSalesService to return a promise that returns the predefined test data when resolved
create the component
await
check snapshot
Testing the looks of SalesChart is not part of this question, I hope to solve that using Enzyme. I have been trying dozens of things to mock this asynchronous call, but I cannot seem to get this mocked properly. I have found the following examples of Jest mocking online, but they do not seem to cover this basic usage.
Hackernoon: Does not use asychronous calls
Wehkamp tech blog: Does not use asynchronous calls
Agatha Krzywda: Does not use asynchronous calls
GitConnected: Does not use a class with a function to mock
Jest tutorial An Async Example: Does not use a class with a function to mock
Jest tutorial Testing Asynchronous Code: Does not use a class with a function to mock
SO question 43749845: I can't connect the mock to the real implementation in this way
42638889: Is using dependency injection, I am not
46718663: Is not showing how the actual mock Class is implemented
My questions are:
How should the mock class look like?
Where should I place this mock class?
How should I import this mock class?
How do I tell that this mock class replaces the real class?
How do set up the mock implementation of a specific function of the mock class?
How do I wait in the test case for the promise to be resolved?
One example that I have that does not work is given below. The test runner crashes with the error throw err; and the last line in the stack trace is at process._tickCallback (internal/process/next_tick.js:188:7)
# __tests__/SalesView-test.js
import React from 'react';
import SalesView from '../SalesView';
jest.mock('../SalesService');
const salesServiceMock = require('../SalesService').default;
const weekTestData = [];
test('SalesView shows chart after SalesService returns data', async () => {
salesServiceMock.fetchSalesData.mockImplementation(() => {
console.log('Mock is called');
return new Promise((resolve) => {
process.nextTick(() => resolve(weekTestData));
});
});
const wrapper = await shallow(<SalesView/>);
expect(wrapper).toMatchSnapshot();
});
Sometimes, when a test is hard to write, it is trying to tell us that we have a design problem.
I think a small refactor could make things a lot easier - make SalesService a collaborator instead of an internal.
By that I mean, instead of calling new SalesService() inside your component, accept the sales service as a prop by the calling code. If you do that, then the calling code can also be your test, in which case all you need to do is mock the SalesService itself, and return whatever you want (using sinon or any other mocking library, or even just creating a hand rolled stub).
You could potentially abstract the new keyword away using a SalesService.create() method, then use jest.spyOn(object, methodName) to mock the implementation.
import SalesService from '../SalesService ';
test('SalesView shows chart after SalesService returns data', async () => {
const mockSalesService = {
fetchSalesData: jest.fn(() => {
return new Promise((resolve) => {
process.nextTick(() => resolve(weekTestData));
});
})
};
const spy = jest.spyOn(SalesService, 'create').mockImplementation(() => mockSalesService);
const wrapper = await shallow(<SalesView />);
expect(wrapper).toMatchSnapshot();
expect(spy).toHaveBeenCalled();
expect(mockSalesService.fetchSalesData).toHaveBeenCalled();
spy.mockReset();
spy.mockRestore();
});
One "ugly" way I've used in the past is to do a sort of poor-man's dependency injection.
It's based on the fact that you might not really want to go about instantiating SalesService every time you need it, but rather you want to hold a single instance per application, which everybody uses. In my case, SalesService required some initial configuration which I didn't want to repeat every time.[1]
So what I did was have a services.ts file which looks like this:
/// In services.ts
let salesService: SalesService|null = null;
export function setSalesService(s: SalesService) {
salesService = s;
}
export function getSalesService() {
if(salesService == null) throw new Error('Bad stuff');
return salesService;
}
Then, in my application's index.tsx or some similar place I'd have:
/// In index.tsx
// initialize stuff
const salesService = new SalesService(/* initialization parameters */)
services.setSalesService(salesService);
// other initialization, including calls to React.render etc.
In the components you can then just use getSalesService to get a reference to the one SalesService instance per application.
When it comes time to test, you just need to do some setup in your mocha (or whatever) before or beforeEach handlers to call setSalesService with a mock object.
Now, ideally, you'd want to pass in SalesService as a prop to your component, because it is an input to it, and by using getSalesService you're hiding this dependency and possibly causing you grief down the road. But if you need it in a very nested component, or if you're using a router or somesuch, it's becomes quite unwieldy to pass it as a prop.
You might also get away with using something like context, to keep everything inside React as it were.
The "ideal" solution for this would be something like dependency injection, but that's not an option with React AFAIK.
[1] It can also help in providing a single point for serializing remote-service calls, which might be needed at some point.

Categories

Resources