Unit testable components pt.2: Redux and separation of concerns
Use global state management, like Redux
Production size codebases are completely unmaintainable if local component state is exclusively used. Don't even try. React +16.3 introduced their own global state management system, the context api. But even then, I find it to be lacking base functionality. If you want those niceties, I don't think you should reinvent the wheel when redux is so functionality-rich with a huge open source community that backs it. I use the features you get from redux to keep my components virtually free of business logic.
Opinion: Thunk is the only Redux middleware needed
There is a lot of middleware out there, but I find redux-thunk to be more than adequate for my needs.
Actions within actions: abstracting and minimizing business logic in components
Run one action inside the component, and have the action perform many operations inside it.
export class Home extends React.Component {
static whyDidYouRender: boolean
componentDidMount() {
this.props.processHomeMount()
}
render() {
return (
<div>
<ShowOfTheDay />
<PopularSearches />
<News />
</div>
)
}
}
const mapDispatchToProps = { processHomeMount }
export default connect(
() => ({}), mapDispatchToProps
)(Home)
That's it. But what does the redux action
processHomeMount
do?
const processHomeMount: types.processHomeMount = () =>
async (dispatch, getState) => {
try {
const type = getState().home.topSearches.focusedDateRange
const { endDate, startDate } = createPresentDateRange({ daysAgo: 100 })
dispatch(fetchPopularSearches({ type, limit: TOP_SEARCH_ENTRY_COUNT })),
dispatch(fetchNews({ startDate, endDate }))
} catch (error) {
throw new Error(error)
}
}
- gets a value from Redux store
- creates two sets of dates to be used as params in one of the api calls
- dispatches two actions that make API calls with a lot of business logic inside each
- try/catch error handling
fetchPopularSearches
and
fetchNews
are entirely different beasts on their own that also contain api calls and algorithmic logic as well
What does our business logic test look like for our
<Home />
component?
// Home.test.jsx
import { shallow } from 'enzyme'
const processHomeMount = jest.fn()
let wrapper
describe(' ', () => {
beforeEach(() => {
wrapper = shallow(<Home processHomeMount={processHomeMount}/>)
})
describe('componentDidMount', () => {
it('invokes home initialization process', () => {
expect(processHomeMount).toBeCalled()
})
})
})
Yes, that's it
My component testing philosophy
An explanation for part 3.
Comments
Post a Comment