tldr; You can test your Redux app by rendering it in node, simulating user interactions and verifying changes in state and markup. These tests are relatively easy to write, fast to run and give a lot of confidence. Writing efficient software tests is a tricky balancing act. By efficiency, I don’t mean execution speed or resource consumption, but rather nailing the trade-off between the effort put into writing tests and the value they provide. This is not a new or unknown problem. A lot of smart people pondered on it in the past, and established guidelines that can help developers tackle it. I’m a big believer in the that dictates the relative number of different types of tests in a healthy test suite, with unit tests being the strong base, covering every piece of code individually. testing pyramid Unit tests and Redux The structure encouraged by Redux makes writing unit tests a breeze. You can require different blocks of the application (think reducers, action creators, containers, etc.) in isolation, and test them like any other pure function — test data in, asserting on the data out, no mocking required. lists unit testing approach for each of these blocks. The testing guide in Redux documentation Following this guide you can get to a complete unit test coverage by tediously copy-pasting tests from reducer to reducer, from action creator to action creator... But once all that work is done, the testing pyramid strikes back. Since it’s only unit tests, the test suite still doesn’t answer the fundamental question — does the app actually work? Climbing the pyramid There are several ways to interpret the upper layers of the testing pyramid in the context of a webapp. The top, end-to-end (e2e) layer can be implemented using Selenium, for example with . These tests are agnostic, so they will still be valid even if you port your app to use a different framework. However, they take long to implement and run, are hard to debug, and can often be flaky. Usually a project can and should only afford a relatively small number of them. webdriver.io technology What about the layer between unit and e2e tests? In general, these are called integration tests, as they verify how different modules in the application work together. The spectrum of integration tests is wide. For example, one could argue that if a test for reducers uses action creators to dispatch actions, it’s already more than a unit test. On the other end of that spectrum, e2e test can be seen as the most extreme case of an integration test. It seems that one could try to find the sweet-spot for integration tests in Redux. Ideally, they should be fast enough to run as a part of the development flow, they should use the same testing infrastructure as the unit tests and they should give a decent level of confidence that the entire part of the webapp managed by Redux works as expected. Finding the boundaries Finding out where to place the boundaries of the tests is a good starting point. The structure of most webapps can be represented like so: Some parts of the system need to be mocked, in order to achieve the desired characteristics of the tests. From the top, the most limiting factor is the browser. Starting an instance of (even a headless) browser to run the tests will take much longer than running some code in node. From the bottom, we don’t want to wait for real requests to complete. The layer is also a clearly defined interface that is reasonably easy to mock. network Mocking the boundaries Assuming the app uses React and Redux, it’s quite easy to write it in a way that allows it to smoothly run in node during testing (or even in production if you’re rendering server-side). This means it’s possible to use the great testing framework to run the tests as well as to render parts, or the entirety of your application and interact with it without the need of an actual browser environment. Jest enzyme Enzyme provides a function which can be used to render and interact with any React component, for instance, a complete Redux app. To reduce the boilerplate for each test, it’s useful to write a simple utility function that renders the app with given state and returns the enzyme’s wrapper object, as well as the Redux store object (which will come handy in assertions). mount Utility for rendering Redux application with arbitrary state, and an example of usage Running the tests in node also enables some clean mocking solutions for the network layer, e.g. the library. Nock makes it easy to declare response data and codes as well as errors for network requests before running specific tests. Mocking a successful GET request can look like this: nock Example of mocking a network request with nock With this setup, it should be possible to run the integration tests with the convenience and speed not that much worse than the unit tests. All that’s left is implementation… Mocked boundaries for Redux integration tests What to test? The kind of integration tests that will give the most confidence in the correct functioning of the app are those that take the perspective of the user. The goal is to verify that once the user interacts with the application by clicking buttons, filling form elements etc., the app responds by modifying itself or performing side effects as expected. Let’s consider a simple scenario of submitting a form. We render the app with the data already filled in, and simulate the user clicking the submit button. We also make sure that the request to the API endpoint that our app is wired to call will succeed. Boilerplate for the integration test with Jest When to test? Before diving into implementation of the assertions, there is one more problem to address: when to run them. In a simple case, when all the changes in the app happen synchronously, you can run the assertions straight after simulating the user action. However, your app will most likely use Promises to handle async part of the code, e.g. the network requests. Even though the request is mocked to resolve synchronously, the success promise handler will run strictly after any code that sits right below the line. We need to wait for our app to “be done” before we start asserting. submitButton.simulate('click') Jest offers several ways of working with async code, but they require either a direct handle to the promise chain (which we don’t have in this example) or require mocking timers (which doesn’t work with promise-based code). You could use which forces us to use , but this will make the test code much less elegant. setTimeout(() => {}, 0) Jest’s async callback feature However, there is a nice solution to this problem in a form of a one-liner utility function that creates a promise resolved on the next tick of the event loop. We can use it with Jest’s built-in support for returning a promise from the test: Elegant solution to running assertions for Promise based code without handle to the Promise chain How to test? What options are there, for verifying the app responded to the user interaction correctly? You can inspect the markup of the page to check that the UI is correctly modified, for example using . Note: for the following test to work you will need to setup a Jest snapshot serializer, for example using . Markup. Jest’s snapshot feature enzyme-to-json package Using Jest’s snapshot feature to verify markup changes This kind of assertions is incredibly easy to write, but tests using them tend to be quite unfocused. The snapshot of the app’s markup will probably change often, making your seemingly unrelated tests fail. They also don’t document the expected behaviour, only verify it. Check the modification to the state of the application. It’s easy in a Redux application with a centralised store, might be more tricky if the state is distributed. Also in this case snapshot could be used, but I prefer the explicitness of object literals. State. Verifying changes in Redux store state This types of assertion is less user-centric, as the store state sits “under the hood” of your application. However, testing like this will be less susceptible to flakiness caused by design-driven changes to markup. Depending on your application, there may be other side effects that you should check (e.g. network requests, changes to ). You could, for example, use the to verify that the request mocks created have been consumed. Side effects. localStorage [isDon](https://github.com/node-nock/nock#isdone)e method from nock This approach takes advantage of one of the strongest features of Redux, the serialisable log of actions. We can use it to assert on the sequence of the actions dispatched to the store, e.g. with help of the useful library. First, the method needs to be modified to use a mocked version of Redux store, so that the store exposes a method. Dispatched actions. redux-mock-store renderAppWithState getActions Verifying the sequence of actions dispatched to the store This type of assertions is useful especially for more complex async flows. It also provides a clear overview of the expected behaviour of the app in the tested scenario, serving as documentation. Finding the balance Introduction of this type of integration tests should not mean skipping unit tests. Most parts, and especially the logic-heavy parts of the application (like reducers or selectors in a Redux app) still require to be thoroughly unit-tested. The pyramid still applies! However, integration tests are a valid addition to the testing toolbox that should help building a healthy test suite that causes as little pain as possible and allows for more confident deployments. The subject of software testing is one of the most opinionated in the industry. When reviewing this article, one of my colleagues pointed by to an article titled “ ”. Some of the points the author makes are valid, but things are not so black-and-white in my opinion. What do you think? Integrated tests are a scam is how hackers start their afternoons. We’re a part of the family. We are now and happy to opportunities. Hacker Noon @AMI accepting submissions discuss advertising & sponsorship To learn more, , , or simply, read our about page like/message us on Facebook tweet/DM @HackerNoon. If you enjoyed this story, we recommend reading our and . Until next time, don’t take the realities of the world for granted! latest tech stories trending tech stories