Updating React context outside of a component
Published on in JavaScript, React and Testing
React context is normally updated inside the React app. Here's a hacky way how I circumvented that restriction in a test file.
Table of contents
Example problem
Let's say I have a React component called MyComponent
which uses a React context called MyContext
.
When the value of the context changes, the component does something, e.g. makes an Adobe Analytics tracking call:
function MyComponent() {
const myContext = useContext(MyContext)
useEffect(() => {
window._satellite.track(myContext)
}, [myContext])
return null
}
And then I want to write unit tests for that... But how can I do that since React contexts can't be easily updated outside of React components?
Hacky workaround
I realized that in the test file,
I can pass an empty object for the wrapper component (App
in this case)
as a prop (named contextUpdater
).
Inside App
,
I can store a reference to the context value setter function (setValue
in this case)
under the given prop object.
Then, in a test case,
I can call contextUpdater.setValue
–
outside of the component!
Like so:
/* MyComponent.test.js */
describe('MyComponent', () => {
function App({ contextUpdater = {} }) {
const [value, setValue] = useState('initial value')
// This hack allows us to update the context value outside of the component
contextUpdater.setValue = setValue
return (
<MyContext.Provider value={[value, setValue]}>
<MyComponent />
</MyContext.Provider>
)
}
beforeEach(() => {
global._satellite = { track: jest.fn() }
})
it('makes a tracking call when MyContext changes', () => {
const contextUpdater = {}
render(<App contextUpdater={contextUpdater} />)
expect(global._satellite.track).toHaveBeenCalledTimes(1)
act(() => contextUpdater.setValue('new value'))
expect(global._satellite.track).toHaveBeenCalledTimes(2)
act(() => contextUpdater.setValue('third value'))
expect(global._satellite.track).toHaveBeenCalledTimes(3)
})
})
It works! IQ 200! Proud moment of the week.
Too hacky?
So yeah, actually this feels very hacky, probably because this is hacky.
Another question is whether the test is too implementation-specific.
On the other hand,
MyComponent
directly depends on MyContext
,
so I'm not sure.
Anyway, the test passes, so I think this is good enough for now.