Testing React Applications with Jest
A regression is step backwards to a less developed state
Regression testing is checking that the new features haven’t broken the existing features
Test files are committed alongside the app code
Without tests:
- Have to test every single page works feature by feature
- Manually testing becomes very expensive
Advantages to testing:
- Prevents regression
- Reduces manual regressions
- Verify obscure data input tests
- Allows developers to focus on current tasks
Disadvantages:
- More code to write
- More tools to learn
- Additional dependencies to the application
- Tests that work on local machine may not work on continuous integration machine
- Have to use tests correctly for them to be of value
Unit Test:
- Tests a single function or service
- Test runner required
Component Test:
- Tests an individual part of an application, e.g. a dropdown
- Verifies the correct appearance of a component
- Strong defence against regression
- Verifies change to component output in response to change in state
- Doesn’t verify interactions between components
Snapshot Test:
- Just protects a component from regressing
- Subtype of component test
- Automatically generated by Jest
- Verifies output matches a past record
End to end Test:
- End user testing
- Measures functionality of the whole system, imitating user clicks
- Runs in a Headless browser
- Best assurance that the app works
Performance Test:
- How long a block of code takes to execute
- Can identify bottlenecks in performance
- Difference in performance between different devices
Coverage:
- How much of codebase is covered by tests
Jest
- Test runner
- Installed by npm
- Made by React team
Expect
- Optional assertion library that can be plugged into Jest
Enzyme
- Works with Jest
- Not a test runner
- Has tools to test React components
- Takes component Dom tree HTML so you can check structure
Jasmine/Mocha
- Jest is built on top of Jasmine/Mocha
- Jest adds mocking and snapshot testing
- Superior assertion library
Jest vs Mocha
- Both run tests, check if all tests pass or not
- Both can run Asynchronous tests
- Jest includes spies as default
- Jest includes snapshot testing
- Jest includes module mocking
Jest vs Jest CLI
- Jest is the test runner
- Jest CLI is a tool you use to configure Jest
NPM Scripts control Jest using commands such as test, test-watch, test-e2e test-update, test-prod
Tests are ‘run’ by CI, devs use ‘Watch’
Creating an npm script ‘test’ is what will run by the CI
Jest looks for directory with name tests
Jest looks for files with suffixes *.test.js or *.spec.js
Tests in own folder:
Easy to distinguish what is testing and what is the app code
Tests amongst the code:
Can’t name test files whatever you like
Possible to isolate tests based on name
Test block, common to use
‘It should … ‘
Watch mode watches for changes in files
BeforeAll runs a block of code before all it statements
BeforeEach runs a block of code before each it statements
AfterEach runs after each it statement
AfterAll runs after all it statements
.skip ignores a test
.only skips the other tests in a file
Async testing:
Invoke the done callback passed to the test
Return a promise from a test
Pass an async function to describe
Mocking:
Reduce dependencies, so faster execution
Prevent side effects during testing
Mock specific data required for a test
Mocks:
Duplicate of an object with no internal workings
Can be automatically or manually created
Same API as original but no side effects
Can also add more advanced features as spies
Mocks have all the same methods as the original object
Any method that returns a promise will still do so
Complex values returned by a method have to be mocked too
Mock functions:
Are also known as spies
Calling the mocked function has no side effects
Number of calls is recorded
Can check which arguments were passed in
Can load mock function with return values
Return value from mock function will approximate original
Mocks files must reside in a directory mocks in the folder of the mocked module
NPM modules and local modules can be mocked
Snapshot testing is used to catch regression. They are easy to use but also too easy to ignore
Testing React Components:
- verify output has not regressed
- Ensure rare corner cases work
- Ensure side effects occur but don’t execute them
- Verify user interactions are handled
Components may or may not
- Have lifecycle methods
- Have internal state
- Generate side effects (api calls etc)
Most testable components have:
- No internal state
- No side effects, best to use sagas or thunks for this
- No lifecycle hooks
Redux and Jest:
- Components don’t generate side effects
- Connect component limits us to dispatching actions
- Actions on their own don’t do anything until caught by a thunk or a reducer or a saga or middleware
- Presentation components are handled by snapshots
- Container components can be isolated as the actions they dispatch don’t do anything
- React redux components don’t have internal state
Testing Container Components:
- Unit tests verify container
- Test container successfully maps state to props
- Testing that interactions trigger actions
Testing presentation components:
React Test Renderer
- Takes React component and outputs the resulting HTML without Dom
- Useful for getting output HTML for snapshot testing
Enzyme
- also outputs HTML without Dom
- Also allows you to test clicks, keyboard input etc
Mocks not needed for React Redux components
Testing Stateful React components:
- Mock dependencies
- Use spies to verify side effects eg api calls
- Mock call to API and check how it was called to check it works ok
- Check side effects are happening without them happening
- Move logic from lifecycle methods to services
- Prevent regression using snapshots
- Inject values by writing mocks for services
- Make stateless components