Mar 31 2020

Integration vs e2e testing

Learn how to differentiate between an integration test vs an end-to-end test. Inspired by real life eureka moments!

e2e-vs-integration.png

I’m writing this blog post on testing because it’s a topic I’m very comfortable with and I recently had a Eureka! moment and I want to document my growth in this area.

This is a question that has bugged me from the first time I wrote an integration test using Cypress. I had a vague understanding that integration and end to end tests interact with the app as a user would. But to what extent, I didn't know.

Cypress itself didn't really help matters with it being branded as an end to end testing tool but then using an /integration folder name in example tests. Is it an e2e or an integration test?

I think I can finally attempt to answer that question now 😄

Integration tests are similar to end-to-end tests in one major way; they both test the app as a user would use it. Apart from that, they are pretty different from each other in what they assert and their overall scope with respect to the application.

Integration tests, in my opinion, should interact with the application from the perspective of a user but should not interact with the network layer, i.e, make a network request to fetch data.

Imagine a slider component built with React. Testing such a component using unit tests would probably result in very unreliable tests that don't say much about how it fits in with the app holistically.

Instead, you'd write a simple integration test that would interact with the slider just like a user would.

You want to ensure that a user can perform the above actions in your applications and that they would see the correct thing every time and you probably don't need to make a network request to fetch content for the slider but you want to be able to assert that those actions are possible.

This is where a tool like Cypress then comes in. Because it has access to all layers of your application, it can stub out network requests and provide the data required for the slider component to function.

You can then interact with the slider component using mocked data. This allows the integration test to remain fast and lightweight while ensuring that your slider component works correctly in the context of your application.

To iterate, integration tests

  • test small sections of the app at a time
  • can bypass the network layer without losing integrity
  • are usually faster
  • Now imagine another scenario. You want to add search functionality to your blog.

    When a user types in the search input box and hits enter they should see a list of results that match their query

    Instead of testing an isolated section of your app, you're now testing for user flows and stories which may involve populating many smaller components with data and seeing if they can work together to complete the desired action.

    Since your search functionality is probably a custom code, you also want to ensure that it meets business requirements. You should not stub this request because you really want to test your backend search API as well as your frontend rendering code. End. To. End.

    Notice how we're involving multiple stacks and asserting that they can work together to satisfy the user story. Stubbing out the network layer would make it impossible to test a significant portion of the feature (the backend code that actually does the searching).

    To iterate, end to end tests

  • test user flows and whole features from end to end
  • good e2e tests require the network layer to be useful
  • are slower and expensive to run compared to integration tests
  • Can you now begin to see how integration and end to end tests operate at different scales? They are similar in a way but are concerned with proving different things.

    Both integration and e2e testing are essential in any significant application. Having one or the other may be sufficient in some cases but you can’t beat the robustness of having both in a project.

    Having both classes of tests will enable you iterate much faster with little worry of breaking existing features and UX flows.