问题
I am trying to fix a failing test in my react-redux app. When I dive and dive again into my component, I expect to see the JSX within it. However, I don't see anything.
Here is my component -
const Dashboard = (props) => {
if (props.isSignedIn)
return (
<div className="dashboard">
<h1>Welcome</h1>
</div>
);
return null;
};
const mapStateToProps = (state) => {
return { isSignedIn: state.auth.isSignedIn };
};
export default connect(mapStateToProps, { signIn, signOut })(Dashboard);
Here is my failing test :-
const setup = (initialState = {}) => {
const store = storeFactory(initialState);
const wrapper = shallow(<Dashboard store={store} />).dive().dive();
return wrapper;
};
describe("on render", () => {
describe("the user is signed in", () => {
let wrapper;
beforeEach(() => {
const initialState = { isSignedIn: true };
wrapper = setup(initialState);
});
it("renders the dashboard", () => {
const component = wrapper.find("dashboard");
expect(component.length).toBe(1);
});
});
My store factory :-
export const storeFactory = (initialState) => {
const createStoreWithMiddleware = applyMiddleware(reduxThunk)(createStore);
console.log(initialState);
return createStoreWithMiddleware(rootReducer, initialState);
};
My test error :-
● the user is signed in › renders the dashboard
expect(received).toBe(expected) // Object.is equality
Expected: 1
Received: 0
When I dive one time it looks like this :-
<Dashboard store={{...}} isSignedIn={{...}} signIn={[Function]} signOut={[Function]} />}
but when I try to see the JSX inside of the dashboard component I see nothing?
回答1:
I'm pretty sure your setup isn't working because you're trying to shallow
mount a redux connected component -- which is a higher-order component (HOC) wrapping another component that can't be properly dive
d into due to enzyme's shallow limitations.
Instead, you have two options:
Option 1.) Recommended: Export the Dashboard
component and make assertions against it using shallow
or mount
(I mostly use mount
over shallow
specifically to avoid excessive and repetitive .dive()
calls).
First export the unconnected component:
export const Dashboard = (props) => {
if (props.isSignedIn)
return (
<div className="dashboard">
<h1>Welcome</h1>
</div>
);
return null;
}
export default connect(...)(Dashboard)
Then in your test, import the Dashboard component (not the default connected export):
import { mount } from "enzyme";
import { Dashboard } from "./Dashboard"; // importing named export "Dashboard"
const initialProps = {
isSignedIn: false
}
const wrapper = mount(<Dashboard { ...initialProps } />); // alternatively, you can use shallow here
// now manipulate the wrapper using wrapper.setProps(...);
or
Option 2.) Not recommended: Wrap the component in a real Provider
with a real store
and mount
the connected HOC:
import { mount } from "enzyme";
import { Provider } from "redux";
import { createStore, applyMiddleware } from "redux";
import thunk from "redux-thunk";
import rootReducer from "../path/to/reducers";
import types from "../path/to/types";
import Dashboard from "./Dashboard"; // importing connected default export
const store = createStore(rootReducer, undefined, applyMiddleware(thunk));
const initialProps = {
isSignedIn: false
};
const wrapper = mount(
<Provider store={store}>
<Dashboard { ...initialProps } />
</Provider>
);
// now you'll dispatch real actions by type and expect the redux store to change the Dashboard component
For more testing information, please take a look at this answer, which covers a similar example (skip to the bullet points; ignore the MemoryRouter pieces; and, while the example is a bit old, the testing pattern is the same).
The reason I'd recommend option 1 over option 2 is that it's much easier to manage, as you're directly manipulating the component you're testing (not a HOC wrapping your component). Option 2 is only useful if you absolutely need to test the entire workflow of redux to the connected component. I find option 2 to be mostly overkill as you can unit test each piece (actions, reducers, and unconnected components) individually and achieve the same testing coverage. In addition, as I mentioned in this example, I find redux-mock-store
to be mostly useful for unit testing redux actions.
On a side note, you can see what enzyme sees by using console.log(wrapper.debug());
in a test!
Example unconnected test:
import React, { createElement } from "react";
import { configure } from "enzyme";
import Adapter from "enzyme-adapter-react-16";
import { mount } from "enzyme";
import { MemoryRouter } from "react-router-dom";
import { Dashboard } from "./App.js";
configure({ adapter: new Adapter() });
const initialProps = {
isSignedIn: false
};
describe("Unconnected Dashboard Component", () => {
let wrapper;
beforeEach(() => {
/*
This code below may be a bit confusing, but it allows us to use
"wrapper.setProps()" on the root by creating a function that first
creates a new React element with the "initialProps" and then
accepts additional incoming props as "props".
*/
wrapper = mount(
createElement(
props => (
<MemoryRouter initialEntries={["/"]}> // use memory router for testing (as recommended by react-router-dom docs: https://reacttraining.com/react-router/web/guides/testing)
<Dashboard {...props} />
</MemoryRouter>
),
initialProps
)
);
});
it("initially displays nothing when a user is not signed in", () => {
expect(wrapper.find(".dashboard").exists()).toBeFalsy();
});
it("displays the dashboard when a user is signed in", () => {
wrapper.setProps({ isSignedIn: true });
expect(wrapper.find(".dashboard").exists()).toBeTruthy();
});
});
Working example (click on the Tests
tab to run tests):
Reuseable HOC wrapper:
utils/HOCWrapper/index.js
import React, { createElement } from "react";
import { createStore, applyMiddleware } from "redux";
import { Provider } from "react-redux";
import { MemoryRouter } from "react-router-dom";
import { mount } from "enzyme";
import thunk from "redux-thunk";
import rootReducer from './path/to/reducers';
const middlewares = applyMiddleware([thunk]);
export const store = createStore(rootReducer, null, middlewares);
/**
* Factory function to create a mounted MemoryRouter + Redux Wrapper for a component
* @function HOCWrapper
* @param {node} Component - Component to be mounted
* @param {object} initialProps - Component props specific to this setup.
* @param {object} state - Component initial state for setup.
* @param {array} initialEntries - Initial route entries for MemoryRouter.
* @param {object} options - Optional options for enzyme's "mount"
* @function createElement - Creates a wrapper around passed in component (now we can use wrapper.setProps on root)
* @returns {MountedRouterWrapper}
*/
export const HOCWrapper = (
Component,
initialProps = {},
state = null,
initialEntries = ["/"],
options = {},
) => {
const wrapper = mount(
createElement(
props => (
<Provider store={store}>
<MemoryRouter initialEntries={initialEntries}>
<Component {...props} />
</MemoryRouter>
</Provider>
),
initialProps,
),
options,
);
if (state) wrapper.find(Component).setState(state);
return wrapper;
};
export default HOCWrapper;
To use it, import the HOCWrapper function:
import Component from "./Example";
import HOCWrapper from "./path/to/utils/HOCWrapper";
// if you need access to store, then...
// import HOCWrapper, { store } from "./path/to/utils/HOCWrapper";
const initialProps = { ... };
const initialState = { ... }; // optional (default null)
const initialEntries = [ ... ]; // optional (default "/")
const options = { ... }; // optional (default empty object)
// use the JSDoc provided above for argument breakdowns
const wrapper = HOCWrapper(Component, initialProps, initialState, initialEntries, options); // not all args are required, just the "Component"
来源:https://stackoverflow.com/questions/61717143/how-do-you-debug-a-shallow-rendered-enzyme-test