I've been working with ApolloJS in React (via react-apollo) for the past several months and have learned a number of the tricks and challenges associated with unit testing Apollo-wrapped components.
When testing a component that is directly wrapped with Apollo, I export and test the component before it is wrapped with HoC returned by graphql. When testing a component that has an Apollo-wrapped component as a descendant, I use Enzyme's shallow rendering whenever possible to prevent that descendant from mounting. If full-DOM rendering via mount is required, I use a MockedProvider from Apollo's test utils so that the descendants don't throw an error trying to access this.context.
I have not found a solution, however, for the following case: a component with Apollo-wrapped descendants needs to be tested using full-DOM rendering but I also need to make assertions involving the component instance (e.g. state, instance methods, etc). To avoid issues with descendants, I have to wrap the component in a mocked provider, but that means than any assertions on the wrapper are operating on the MockedProvider instance and not the component I want to be testing.
An example:
import { mount } from 'enzyme'
import { MockedProvider } from 'react-apollo/lib/test-utils'
// This component has descendants that are wrapped in Apollo and
// thus need access to `this.context` provided by an Apollo provider
import Assignments from 'app/components/assignments
...
describe('<Assignments />', function() {
it('sets sorted assignments in initial state', function() {
const assignments = [...]
const wrapper = mount(
<MockedProvider>
<Assignments assignments={assignments} />
</MockedProvider>
)
// This will fail because the wrapper is of the MockedProvider
// instance, not the Assignments instance
expect(wrapper.state('assignments')).to.eql([...])
})
})
I've tried to find a way via Enzyme to access the component instance of a child rather than the root, which as far as I can tell is not supported. I've also been trying to find alternatives to needing the MockedProvider in these tests but have not found anything yet.
Has anyone figured out a workaround for this sort of situation, or is there a different approach I should be taking to deal with nested Apollo-wrapped components?
I've found a solution to my problem. The reason Apollo-wrapped descendants of mounted components were causing problems is that they would throw an error when trying to access this.context.client. Apollo's MockedProvider creates an Apollo client (or optionally uses one you provide) and makes it available to its children via context.
Turns out Enzyme's mount method allows you to specify the component's context. I had tried using that before, but didn't realize that I also needed to combine it with the childContextTypes for that context to be passed down to the mounted component's descendants. Using these Enzyme options avoids the need to use the MockProvider.
I'll demonstrate the solution based off the example provided in my original question:
import React from 'react'
import { mount } from 'enzyme'
// This is an Apollo client I configured in a separate module
// with a mocked network interface. I won't go into details on
// that here, but am happy to provide more details if someone asks
import mockedClient from 'test/mocked_client'
// This component has descendants that are wrapped in Apollo and
// thus need access to `this.context` provided by an Apollo provider
import Assignments from 'app/components/assignments
...
describe('<Assignments />', function() {
it('sets sorted assignments in initial state', function() {
const assignments = [...]
const wrapper = mount(
<Assignments assignments={assignments} />,
{
context: { client: mockedClient },
childContextTypes: {
client: React.PropTypes.object.isRequired
}
}
)
// This now passes!
expect(wrapper.state('assignments')).to.eql([...])
})
})
Hopefully this helps someone who finds themselves in a similar situation!
Related
How can I implement #inject and observer in functional child components without explicitly importing the store with context and destructuring it. Repo and Deployed site below
https://laughing-mayer-7a7c36.netlify.app/
https://github.com/hildakh/testmobx
You can make hook like that, so you won't need to import context every time, just this hook:
export const useStore = () => {
const store = React.useContext(storeContext)
if (!store) {
// this is especially useful in TypeScript so you don't need to be checking for null all the time
throw new Error('useStore must be used within a StoreProvider.')
}
return store
}
And you can still use inject decorator with functional components if you prefer that way, it still works and it's totally fine way
I am completely new to react-testing-library. I just started reading all the various "Getting Started" documentation and blog posts I could find after I had no success testing a component with Enzyme. Most of the examples I could find are pretty simple, like those in the "Introducing the react-testing-library" blog post. I would like to see examples of how to test a component that itself is composed of other components, since Component composition is one of the greatest things about React (in this SO post I will call an example of such ComposedComponent for lack of a better name).
When I wrote tests for a ComposedComponented in Enzyme, I could just assert that the correct props were passed to some ChildComponent and trust that ChildComponent had its own tests and I would not have to be concerned with what ChildComponent actually rendered to the DOM within my tests for ComposedComponent. But with react-testing-library, I am concerned that since "rather than dealing with instances of rendered react components, your tests will work with actual DOM nodes", I will also have to test the behavior of ChildComponent by making assertions about the DOM nodes it renders in response to its relationship to ComposedComponent. That would mean that the higher up I go in the Component hierarchy in a React application, the longer and more exhaustive my tests would become. The gist of my question is this: How can I test the behavior of a component that has other components as children without also testing the behavior of those child components?
I truly hope that I am just suffering from a failure of imagination and somebody can help me figure out how to properly use this library that has gained such a following as a replacement for Enzyme.
What I do when testing components that happen to render other (already tested) components is mock them. For example, I have a component that displays some text, a button, and a modal. The modal itself is already tested so I don't want to test it again.
import React from 'react';
import { render, fireEvent } from '#testing-library/react';
import { ComponentUnderTest } from '.';
// Mock implementation of a sub component of the component being tested
jest.mock('../Modals/ModalComponent', () => {
return {
__esModule: true,
// the "default export"
default: ({ isOpen, onButtonPress }) =>
isOpen && (
// Add a `testid` data attribute so it is easy to target the "modal's" close button
<button data-testid="close" onClick={onButtonPress} type="button" />
),
};
});
describe('Test', () => {
// Whatever tests you need/want
});
I'm trying to use Jest to integration test some of our more complex react components which are made up of a number of sub components, the theory being to test high up the stack of components to be closer to how the user experiences the functionality.
I'm using redux for state management and calls are made to sagas as the component is mounted and state updated once the ajax calls are returned via fetch calls in the sagas.
The problem I'm having is when I build my component in Jest I'm only getting the inital state of the component and its like none of the saga's are being called - or at least the results of their fetch calls are not being correctly mocked.
I can see the saga's are being called from debugging statements.
My test is:
import React from 'react';
import {fireEvent, cleanup, waitForDomChange, wait} from '#testing-library/react'
import {renderWithRedux} from '../../../../helpers/testSetup'
import "#testing-library/jest-dom/extend-expect"
import {ComponentContainer} from '../componentContainer'
import {
ajaxCall1,
ajaxCall2,
ajaxCall3
} from '../../../../__mocks__/componentMockData'
// test setup
afterEach(cleanup)
beforeEach(() => {
fetch.resetMocks();
});
// consts
const match = {
params: {
id: "23423"
}
}
// tests
describe('Component Container', () => {
it('Rendering component', async () => {
// 1. Arrange
fetch
.once(ajaxCall1())
.once(ajaxCall2())
.once(ajaxCall3())
// building my component here
const {getAllByText,getByTestId,findByTestId,store} = renderWithRedux(<ComponentContainer match={match} />)
expect(getAllByText("Loading component")[0]).toBeVisible()
await findByTestId('componentHeaderCard') // this times out
// 2. Act
// 3. Assert
})
})
From my research I don't seem to be able to find something which tests building a component which inside makes a number of calls - only tests which mock one fetch.
Is what I'm attempting possible?
This might not be the "best" solution but it has worked.
Instead of having different fetch calls within my sagas I moved them out into their own functions which I then call within my saga. This has allowed me to mock those individual functions.
In an integration test I want to test that a connected action creator gets called.
describe('SomeContainer', () => {
let subject, store, fancyActionCreator
beforeEach(() => {
store = createStore(combineReducers({ /* ... */ }))
fancyActionCreator = sinon.spy()
const props = {
fancyActionCreator
}
subject = (
<Provider store={store}>
<SomeContainer {...props} />
</Provider>
)
})
it('calls fancyActionCreator on mount', () => {
mount(subject)
expect(fancyActionCreator.callCount).to.equal(1)
})
}
The action creator is called inside componentWillMount and works as expected beyond the test environment.
The problem is that the original action creator gets called in the test and does not get mocked away properly.
I've the feeling it's because of Redux's connect() method that is replacing the spy:
connect(mapStateToProps, { fancyActionCreator })(SomeContainer)
You mounted your component with store. If you take return value from mount call, it gives you enzyme wrapper for react element. This wrapper can be used to dispatch actions against store:
const enzymeWrapper = mount(subject)
enzymeWrapper.node.store.dispatch({ type: "ACTION", data: "your fake data" });
But this is more integration type of testing, because you are using reducers as well as connection of Redux store state to your properties.
This is the only test I could come up to test connection of Redux store state to component properties. If you fake properties somehow else, you might cover your component logic, but you are missing piece that is connecting properties to store.
I would suggest to separate your components into presentational vs container components. Presentational don't need to use store and thus you can hammer its logic just by passing different properties. Container component concern is to connect store to presentational components. So for container component you would use type of testing I described.
REACTION ON COMMENT:
Actually usage of mount vs shallow for presentational/unconnected component is not that that straight forward. Sometimes you are using sub-components on your presentational component that needs to be rendered by mount (e.g. react-select needs DOM for some reason).
But generally yes, one should strive to use shallow for presentational components, unless you realize you need mount :).
I have the following situation where I have the following functionality I'd like to test, however, I can't seem to locate in the debug statements nor Enzyme's docs on how to achieve this:
wrapper = mount(
<SampleProvider data={data}>
<App />
</SampleProvider>
);
and I have App wrapped in a higher order function as follows:
#applyData
class App extends Component {
render() {
return <div>App</div>
}
}
I'd like to first, test whether applyData is correctly passing data in as props and the ability to check other conditions, etc. In short, the provider sets data inside of context and applyData basically passes the data as props into the wrapped component.
I've tried mount, shallow, etc... however, I can't get the information I need with any of the following:
wrapper.find(App)
wrapper.children()
wrapper.instance()