In programming whenever we are building any system we should always back it with proper testing. The intention behind these tests should be to cover what ever we have written. It helps in catching regression issues when we are working on different parts of the application. These tests vary in their scope and can have different intentions. For any successful piece of code a good mix of different testing strategy is a must to ensure we have a proper regression suite which can catch issues fairly early in the development cycle.
Few of the tests which we use day in day out are
- Unit Tests
- Integration Tests
- Functional Tests
- Load Tests
- etc.
In this blog post I will try to cover unit and integration tests. The main area of focus will be on
- Purpose
- Scope
UNIT TESTS
Unit tests as the name suggest, test just a unit of code, no more no less. That unit in a programming language like Java can be a method but can vary from solution to solution.
Unit tests are the most basic form of tests and has the most restricted scope in terms of the functionality to test. Unit tests are generally written to test a piece of code which is devoid of any external call which is considered as a separate unit w.r.t the scope of testing at hand. Few examples of things we can leave out in our unit testing are
- external calls to things like databases, services
- Calls to different layers of a multi-layered application like service layer, DAO layer, Controller layer etc
INTEGRATION TESTS
Integration tests cover more functionality than unit tests and as the name suggests the main intention is to test how different unit integrate and talk to each other. To give you an example these different units can be:
- Two different methods in different layer of the application
- Application Code with external component like a database, service
Integration tests can also leave out some components in case you want to restrict your integration tests to verify some specific component interactions. For example suppose your application is calling different services and we want to test integration with just one service. In such a case we can mock all the other services. This is still a integration tests as the intent here is to test the code while our application is calling the actual service and hence covers the integration logic between two components.
As integration tests depends on external dependencies they require a well maintained pre prod environment to test again. In case you want to over come this requirement you can check my other blog where I have demonstrated how to use dockerised services to use in our integration tests.
HOW TO DESIGN YOUR TESTS
As per my experience unit tests and integration tests design is very contextual and hence can very from project to project. However the idea is still the same across all the projects and that is if we want to test a unit of code leaving out all the external calls it is a unit tests.
In integration tests we tests our functionality while it is working and integrating with external parties in our system. In integration tests also we can mock some calls but the intent here is to single out one integration point rather than testing a single unit. Integration tests helps us in figuring out integration pain points and thus should be designed in such a way that we can cover all our integration touch points and thus can avoid fairly trival problems like interface mismatch, connectivity issues, encoding issues etc.
DEMO
I have come up with a small demo. The application is a small student REST service which saves student data in a mongo db instance.
UNIT TEST : StudentServiceUnitTest:
@SpringBootTest
@RunWith(SpringRunner.class)
public class StudentServiceUnitTest {
// Even though we are creating the mock after actually creating the enclosing
// object, still mockito will be able to do this perfectly.
@InjectMocks
StudentService service;
@Mock
StudentRepository repository;
@Test
public void createStudentSuccessTest() {
Student student = Student.builder()
.rollNumber("UE6349")
.firstName("Pulkit")
.lastName("Gupta1")
.age(31)
.build();
when(repository.save(student)).thenReturn(student);
Student student1 = service.saveStudent(student);
Assert.assertNotNull(student1.getRecordIdentifier());
}
}
In this class we are testing the save functionality but we are mocking out the mongo repository. Thus there will be no writes to mongo in this case and we can simply test what ever we have in the save call without integrating with external dependency like a mongo db. Suppose in case we have more external calls in the save method we can mock them as well and can only test the things we are doing only in the save method.
INTEGRATION TEST: StudentServiceIntegrationTest
@SpringBootTest
@RunWith(SpringRunner.class)
public class StudentServiceIntegrationTest {
@Autowired
StudentService service;
@Autowired
StudentRepository repository;
@Test
public void createStudentSuccessTest() {
Student student = Student.builder()
.rollNumber("UE6349")
.firstName("Pulkit")
.lastName("Gupta1")
.age(31)
.build();
Student student1 = service.saveStudent(student);
Assert.assertNotNull(student1.getRecordIdentifier());
}
}
In this case we want to test the integration with the actual DB instance and so we will not be mocking anything. In this case if we are running the tests we will see actual writes happening in the database. Once these tests are executed we can be sure that our code is integrating with Mongodb correctly and is writing data as per our requirement.
CONCLUSION
A good test which covers all our functionality is super important part for building a great and maintainable software. You can tweet the unit tests and integration tests as per your context. The tricky part is to decide what to mock out and which actual calls to use to come up with a good test suite.