Intro
Introduction to software integration testing in the React ecosystem with react-testing-library and jest.
Definitions and required theory
Before you read this article please make sure that you understand unit tests concepts - here you have Unit tests review.
An integration test is a type of software testing that focuses on evaluating the interactions and interdependencies between different components or modules of a software application. The primary goal of integration testing is to ensure that these individual parts of the software work together correctly as a whole, rather than just testing them in isolation.
Here are some key points to help you understand integration testing:
- interaction points - communication of the modules, where it happens, how it happens,
- scope - the part of the feature to check,
- testing real-world scenarios - integration tests verify business cases, flows, whole or part of the program,
- stubs and mocks - like in unit tests they may require stubs and mocks,
- identification of issues - they should try to detect a real business/complete feature problems.
If you want to check the different cases and many unit, integration, and e2e tests - check the Dream stack for React dev repository.
To understand what stubs and mocks are, check the following articles: Understanding stubs in testing and Understanding mocks in testing.
Understanding the feature
Before you start writing integration tests you need to understand the whole or part of the feature that you want to cover. Without this is really hard to write bulletproof tests that will give you real value (will detect problems in your modules communication that may break your feature).
So, let's introduce our feature - the sign-in one.
Yea, again this repetitive concept but it's really great to show how integration tests work.
Loading
The demo of sign-in feature
Let's forget about implementation and focus on parts that are worthy to cover.
- Is the loader shown and the submit button is disabled during submission?
- Is an alert with an error displayed when credentials are wrong or another server error occurs?
- Is an alert with success displayed when the user is signed in?
The current implementation of sign-in
There is no need to deep dive into each module that we're using here - the most important part is that we are relying on other modules. In the example below, we're using Zustand stores, some actions, presentational components, and simple models.
If you want to understand the Zustand a little bit more, I recommend following 🥇 Comparing Redux with Zustand for state management in React article.
Loading
It's a typical scenario. The business state is outside of React ecosystem (implemented with Zustand), we have some dummy presentational components imported from design system library, and we're using custom hooks and other utils.
I skipped the implementation of other modules... Why I did that? Because in integration tests, they should be just black boxes for us. We shouldn't know "how they are implemented", but we need to focus on what are they offering to our consumer code - in this example SignInForm component.
Breaking the walls - let's write the first integration test
We know less or more how our feature works, what stuff is worthy of covering, and how our sign-in form is implemented. Now it's time to write well-defined integration tests for the SignInForm component.
Let's start from defining describe and it blocks.
Loading
We defined 3 cases to cover, but why do we add only 2 scenarios? It's because we'll check the disable of button during success flow and failure flow. They are different user journeys! So, separating this check into separate suites may not detect the issue for a particular journey - the button will be disabled only when submitting in success flow, but won't be disabled in failure one.
Now, we'll start from the integration test that verifies success flow and checks whether the button is disabled or not.
Loading
There are 2 problems with our test:
- we're not checking the alert message occurrence,
- under the hood the is a real HTTP request call.
Mocking the HTTP service layer
To understand what I'm talking about we need to review an action code of Zustand store.
Loading
To avoid HTTP request we need to mock the entire @system/blog-api module. It's not perfect, I know, and how we can improve that will be explained in the next lesson. Right now, let's just finish our test suites to understand the basics.
Loading
We completed our first integration test! Now it's time for the second journey - an error case.
Loading
We added weird statement in this test: jest.requireActual('@system/blog-api')['getError']. This perfectly shows, how bad is leaking into testing the internal implementation details of the module that our code under tests relies on.
This weirdo restores the original implementation for the getError function that is used by our signIn action to parse the failure scenario and retrieve the message from the response. Remember at the beginning of the file we mocked the entire @system/blog-api module - so the getError function is mocked too!
If you're interested in correct way right now, the one with MSW, just go under the following repository and check how it differs.
Making sure that tests are working
It's the best part of software testing. Try to break your implementation, remove some flags, functions, or change their implementation, to be sure that your tests spot problems with your code.
Loading
Dealing with broken implementation
This part is of course optional and if you have more practice, knowledge and experience, you'll mostly trust your tests.
Conclusions
Right now you understand integration tests theory and key points. You know how to write basic tests and how to localize paths to cover, but there is an important question: Are these tests good?
From the perspective of feature covering, they check all important aspects, but there are implementation details leak into our tests - a mock of the signIn service function, that's not good.
To improve our code we need MSW library, but this topic will be covered in the Using MSW library to remove implementation details from tests lesson.
Knowledge requires time, so before you will make things perfect, you need to fail and learn from mistakes.
- 1. Basics
10 minutes
Software testing
2 m
Grouping the tests
1 minute
The usage of describe and it
2 m
The best practices for naming tests
2 m
Navigating the different types of software tests
3 m
- 2. Mastering unit testing
38 minutes
Project and tests setup
3 m
Unit tests review
4 m
React component testing
5 m
Snapshot testing in React
4 m
Understanding stubs in testing
3 m
Understanding mocks in testing
5 m
Creating testing fixtures
4 m
Using spies in React and Typescript
3 m
Mocking environment variables
3 m
Using dependency injection pattern to improve fixtures
4 m
- 3. Mastering integration testing
12 minutes
Understanding the integration tests
4 m
Using MSW library to remove implementation details from tests
4 m
Creating fixture for MSW to reduce boilerplate and setup
4 m
- 4. Mastering e2e tests
8 minutes
Comments
Add your honest opinion about this article and help us improve the content.