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
The two inner actions 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

Popular posts from this blog

Contrasting var and ES6s new declarations: let and const

Modularity vs. Abstraction. Which is More Important?

Unit testable components pt.3: Testing components