Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Behavioral Testing in React and Redux

Behavioral Testing in React and Redux

Re-examining the unit test.

In traditional object-oriented design, writing a “unit test” means instantiating your class and testing each method one by one, usually mocking out all other dependencies in the system. In a React application following the Flux architecture, the basic assumptions about what a unit test is changes. I will examine the architectural principles enabling this paradigm shift, and what testing in a React/Redux can look like.

Ryan Oglesby

October 11, 2017
Tweet

More Decks by Ryan Oglesby

Other Decks in Programming

Transcript

  1. BEHAVIORAL TESTING IN
    REACT & REDUX
    RE-EXAMINING THE UNIT TEST

    View Slide

  2. WHAT DO WE WANT OUT OF OUR TESTS?
    ▸ Confidence
    ▸ The application works as expected
    ▸ Refactor without changing functionality or tests

    ▸ Documentation
    ▸ Use the language of the business
    ▸ Demonstrate conformance to business objectives (user flows, journeys)

    ▸ Act like an application
    ▸ Run fast
    ▸ Easy to maintain

    View Slide

  3. LET’S TALK ABOUT
    OBJECT-ORIENTED
    UNIT TESTING

    View Slide

  4. public class OrderService {
    public Order open(OrderData newOrderData) {
    }
    public boolean submit(String orderId) {
    }
    private boolean validate(Order order) {
    }
    }

    View Slide

  5. Object-oriented design treats the
    class as the unit.

    The methods are the surface area.
    CLASS AS THE UNIT

    View Slide

  6. CLASS AS THE UNIT
    TREE OF CONTRACTS = HIGH SURFACE AREA
    Orders
    Controller
    Products
    Controller
    Order
    Service
    Product
    Service
    Product
    Order
    Product
    View
    Order
    View

    View Slide

  7. CLASS AS THE UNIT
    ORGANIZE BY LAYER

    View Slide

  8. CLASS AS THE UNIT
    WHAT HAPPENS WHEN WE TEST LIKE THIS?
    ▸ Establish contracts between classes.
    ▸ Internal refactoring is easy. Surface area refactoring can
    quickly have ripple effects.
    ▸ Tests run very fast if providing test doubles for expensive
    dependencies.
    ▸ Balance speed vs coverage.
    ▸ Need wider tests to cover the interaction patterns of
    multiple classes.

    View Slide

  9. LET’S TALK ABOUT
    REACT & REDUX

    View Slide

  10. REACT
    REDUX
    FLUX/REDUX ARCHITECTURE
    COMPONENTS REDUCERS
    STORE
    dispatch actions
    provides state to

    View Slide

  11. REACT
    REDUX
    FLUX/REDUX ARCHITECTURE
    VIEW AS A FUNCTION OF STATE
    FUNCTION
    STATE VIEW
    REACT DOM

    View Slide

  12. REDUX
    FLUX/REDUX ARCHITECTURE
    VIEW AS A FUNCTION OF STATE
    STATE
    ‣100% of application state stored in the Redux store
    ‣State always comes from 1 location

    View Slide

  13. REACT
    FLUX/REDUX ARCHITECTURE
    VIEW AS A FUNCTION OF STATE
    FUNCTION
    ‣ Components are deterministic, stateless, predictable
    ‣ Components only have a contract with the Redux state

    View Slide

  14. FLUX/REDUX ARCHITECTURE
    VIEW AS A FUNCTION OF STATE
    VIEW
    REACT DOM
    ‣The View is a representation of the UI
    ‣React DOM is a trusted third party

    View Slide

  15. // CartComponent.jsx
    class Cart extends React.Component {
    componentDidMount() {
    }
    handleClick() {
    }
    render() {
    }
    }
    export default Cart

    View Slide

  16. // CartContainer.js
    function mapStateToProps(state) {
    }
    function mapDispatchToProps(dispatch) {
    }
    export default connect(
    mapStateToProps,
    mapDispatchToProps
    )(Cart)

    View Slide

  17. // cartReducer.js
    function addToCartAction(data) {
    }
    function cartReducer(state, action) {
    }
    export default cartReducer

    View Slide

  18. Object-oriented design treats the
    class as the unit.

    The methods are the surface area.
    CLASS AS THE UNIT

    View Slide

  19. React/Redux design treats the
    function as the unit.

    The functions are the surface area.
    FUNCTION AS THE UNIT

    View Slide

  20. // CartComponent.jsx
    class Cart extends React.Component {
    componentDidMount() {
    }
    _handleClick() {
    }
    render() {
    }
    }
    export default Cart
    // cartReducer.js
    function addToCartAction(data) {
    }
    function cartReducer(state, action) {
    }
    export default cartReducer
    // CartContainer.js
    function mapStateToProps(state) {
    }
    function mapDispatchToProps(dispatch) {
    }
    export default connect(
    mapStateToProps,
    mapDispatchToProps
    )(Cart)

    View Slide

  21. // cartActions.spec.jsx
    describe('Cart Action Creators', () => {
    it('creates an add to cart action', () => {
    const action = addToCartAction('a product name')
    expect(action).toEqual({
    type: 'ADD_TO_CART',
    productName: 'a product name'
    })
    })
    })
    // Cart.spec.jsx
    describe('Cart Component', () => {
    it('handles the click of the checkout button', () => {
    const cart = shallow(

    ).instance()
    cart.handleClick()
    expect(mockDispatch).toHaveBeenCalledWith(stubAddToCart)
    })
    })

    View Slide

  22. FUNCTION AS THE UNIT
    WHAT HAPPENS WHEN WE TEST LIKE THIS?
    ▸ Small refactoring can produce false positives in tests.
    ▸ A lot of low value tests that must change often.
    ▸ Encourages unnecessary mocking of inexpensive,
    deterministic dependencies (Redux store)
    ▸ Tests don’t reflect business language or objectives.
    ▸ Need wider tests to cover interaction of component with
    its own container, actions, and reducer

    View Slide

  23. Redux has changed how we think
    about single page apps,


    now we need to rethink testing.
    RE-EXAMINE THE UNIT

    View Slide

  24. The unit is based on your understanding
    of the system and its testing.

    The view is the surface area.
    RE-EXAMINE THE UNIT

    View Slide

  25. REACT
    REDUX
    OUR UNDERSTANDING OF THE SYSTEM
    VIEW AS A FUNCTION OF STATE
    FUNCTION
    STATE VIEW
    REACT DOM

    View Slide

  26. VIEW AS THE UNIT
    1-WAY CONTRACT = LOW SURFACE AREA
    Cart
    ProductPrice
    BuyButton
    OrderConfirmation
    STATE

    View Slide

  27. VIEW AS THE UNIT
    ORGANIZE BY COMPONENT

    View Slide

  28. VIEW AS THE UNIT
    BEHAVIORAL COMPONENT TEST
    // Cart.spec.jsx
    describe('Cart', () => {
    it('requires login before checking out’, {
    dispatch(
    addToCartAction('iPhone 8')
    )
    const cart = shallow()
    cart.find('#checkout').simulate('click')
    expect(cart).toHaveText('You must login first.')
    })
    })

    View Slide

  29. VIEW AS THE UNIT
    WHAT HAPPENS WHEN WE TEST LIKE THIS?
    ▸ Refactor confidently without changing tests.
    ▸ Test not opinionated about contents of Redux store,
    action shapes, or the names of props.
    ▸ More realistic tests without the need for mocks
    ▸ Better documentation of the business language and
    user flows
    ▸ Write fewer tests. Less code to maintain.

    View Slide

  30. VIEW AS THE UNIT
    WHAT ENABLES THIS KIND OF TESTING?
    ▸ Trust in the system and third parties
    ▸ Strict adherence to React & Redux principles
    ▸ Most components are connected directly to state
    ▸ Rarely pass props to children
    ▸ Most components to not hold local state (no setState)
    ▸ Enzyme and jsdom unlock fast access to the view

    View Slide

  31. SUBCUTANEOUS FUNCTIONAL TEST
    describe('Checkout', () => {
    jest.mock('../httpClient', stubHttpClient)
    it("allows a purchase after logging in", async () => {
    expect.assertions(1);
    const app = mount()
    fillIn(app, '#search', 'iPhone 8')
    await httpReturns({name: 'iPhone 8', price: 899.99})
    click(app, '#addToCart')
    click(app, '#checkout')
    fillIn(app, '#username', 'jane_doe')
    await httpReturns({success: true})
    expect(app).toHaveText('Thanks for your purchase, Jane!')
    })
    })

    View Slide

  32. THEMES
    ▸ The unit tests in a system are dependent on the
    paradigms and architecture of that system.

    ▸ Test observable behavior in the UI, not function return
    values.
    ▸ Save your mocks for the expensive things.
    ▸ Focus on enabling safe refactoring without being forced to
    change tests or wonder if things are working.

    View Slide

  33. Thanks! Ryan Oglesby

    [email protected]
    @ryanoglesby08

    View Slide