Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

LET’S TALK ABOUT OBJECT-ORIENTED UNIT TESTING

Slide 4

Slide 4 text

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

Slide 5

Slide 5 text

Object-oriented design treats the class as the unit.
 The methods are the surface area. CLASS AS THE UNIT

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

CLASS AS THE UNIT ORGANIZE BY LAYER

Slide 8

Slide 8 text

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.

Slide 9

Slide 9 text

LET’S TALK ABOUT REACT & REDUX

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

Object-oriented design treats the class as the unit.
 The methods are the surface area. CLASS AS THE UNIT

Slide 19

Slide 19 text

React/Redux design treats the function as the unit.
 The functions are the surface area. FUNCTION AS THE UNIT

Slide 20

Slide 20 text

// 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)

Slide 21

Slide 21 text

// 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) }) })

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

Redux has changed how we think about single page apps,
 
 now we need to rethink testing. RE-EXAMINE THE UNIT

Slide 24

Slide 24 text

The unit is based on your understanding of the system and its testing.
 The view is the surface area. RE-EXAMINE THE UNIT

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

VIEW AS THE UNIT ORGANIZE BY COMPONENT

Slide 28

Slide 28 text

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.') }) })

Slide 29

Slide 29 text

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.

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

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!') }) })

Slide 32

Slide 32 text

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.

Slide 33

Slide 33 text

Thanks! Ryan Oglesby
 [email protected]
 @ryanoglesby08