Bootiful Test Driven Development

Software engineers have been ardently following Test Driven Development (TDD) as an XP practice for having necessary safety nets. I have even tried covering different schools of TDD with an example in one of my previous posts. Considering recent surge in using Spring Boot for developing Microservice applications, I felt a need to understand and learn how to do TDD whilst implementing Spring Boot application.

In order to understand how Spring Boot simplifies the overall process of doing TDD, we will consider a very simple use case –

  1. Given a reservation system, when user enters details of the user which needs to be searched, it fetches required user details i.e. First name and Last Name
  2. Extend above use case to ensure that caching is being used to optimize lookup operation

End to End Test – 1st Version

By following Outside-In / Mockist school of TDD, we will start with an end to end test – Needless to say that it will fail initially. Of course we will need to ensure that it is compilation error free.

@SpringBootTest – Annotation that can be used for testing Spring Boot application. Along with Spring’s TestContext framework it does following –

  • Registers TestRestTemplate / WebTestClient that can be used for running application
  • Looks for @SpringBootConfiguration for loading spring configuration from the source. One can not only have custom configuration but also alter the order of configurations
  • Supports different WebEnvironment modes

Presentation Layer

Next we start with unit tests which will eventually help us in successfully executing the above end to end test. Since we have adopted Outside – In approach, we will first start with unit testing of REST endpoint. We will be strictly adhering to one of the fundamental rule of TDD

“No Production code without any test”

So in order to make end to end test pass, we will need to have missing pieces. The first missing piece that we will require is the API endpoint i.e. REST controller. We will first start with the controller test

In order to run the test, above test has to be made free of any compilation error. So this leads us to creation of actual endpoint.

Now that we have an endpoint, we will be able to execute test case and we are sure that it is going to fail 🙂 as it is not returning null currently.

Since the Controller is going to further delegate its task of business processing to the underlying Service. We are able to see that Outside-In / Mockist approach of doing TDD is helping us to determine collaborators required by class under test. Hence we will be creating ReservationService with required API which will be devoid of any behavior for time being – As its actual behavior will be discovered when we start writing unit test case for ReservationService. So we create the collaborator to get our class under test i.e. ReservationController compilation error free

We also update ReservationController by wiring required dependency

However, as per the rule of Mockist approach – dependencies (within application) for a Class Under Test should be mocked whilst performing Unit Testing. So we need to update ReservationControllerTest –

When we run above test . . . . . It passes – which means that Controller implementation is as per its expected behavior. So now we have some kind of safety net for our ReservationController . . Vola !

However, one might still feel that test driving happy path flows is relatively easier than business exceptions being thrown during workflow execution. So we will write a test case for a scenario which might end up in a scenario where in user is not available in our system i.e. ReservationNotFoundException. Also our unit test case will have to mock this exception

In order to pass the newly added above test case, ReservationController also needs to be updated by having required behavior for handling exceptions

Within the purview of our feature, this completes the unit testing of REST endpoint i.e Controller. Now we move to business layer i.e. ReservationService which has been discovered as a collaborator of ReservationController. 

Business / Service Layer

Since ReservationService  is a plain Spring bean, we just need to write a plain unit test which is devoid of any Spring dependency – and of course it will fail as our class under test i.e. ReservationService is returning null.

In real world application, business layer i.e. Service class will just be an orchestrator of business workflow, which needs to be executed for returning back required response to Controller. For the sake of simplicity our service layer will just be responsible for fetching required information from database. Hence it will need ReservationRepository as a collaborator, which will be able to fetch required data from database. So we will be creating corresponding Repository which just ensures that my class under test i.e. ReservationService is free from any compilation errors

So with the introduction of new above collaborator, ReservationService would look like

Now coming back to ReservationServiceTest which is now free of compilation errors; we will mock collaborator of ReservationService i.e. ReservationRepository

Lets also verify exceptional flows within this Service class. So we implement test case for the scenario where in ReservationRepository is returning null

Of course this test case will fail as our current implementation of ReservationService is not having required logic for handling no results returned by ReservationRepository. Hence we update our ReservationService 

Within the purview of our feature, this completes the unit testing of ReservationService.

Repository

Now we move to the respository layer i.e. ReservationRepository which has been discovered as a collaborator of ReservationService. 

Even though from implementation standpoint it might seem trivial to test – but its other way round. Reason being, lot of complexity is camouflaged behind CrudRepository which needs to be considered whilst implementing test case for a Repository. In addition we will also need a mechanism to generate test data without database – Many thanks to H2 Database which can be used for our development purpose and also to Spring Boot starter which helps in getting required dependencies of H2 Database. Just to reiterate, we will start with a failing test and then add required implementation to pass this test.

@DataJpaTest – Spring annotation that can be used for a JPA test. This will ensure that AutoConfiguration are disabled and JPA specific configurations are applied. By default it will use H2 database which can be changed by using @AutoConfigureTestDatabase and application.properties. One can change database according to environment by using Spring profile

TestEntityManager – Along with few methods of EntityManager it provides helper methods for testing JPA implementation. We can customize TestEntityManager by using @AutoConfigureTestEntityManager. 

End to End Test – Final Version

We are still remaining to get our ReservationEndToEndTest pass 🙂 So we will update this test such that H2 database is populated with required data which can be used for asserting post invocation of API endpoint. And by this we are able to complete our feature implementation with Outside – In / Mockist style of TDD

Testing Non Functional Requirement – Caching

In order to test caching, first we will need to enable caching within our Spring Boot application via @EnableCaching i.e.

@EnableCaching – Responsible for registering necessary Spring components based on @Cacheable annotation

Next thing that needs to be done is – we annotate our business API i.e getReservationDetails() within ReservationService with @Cacheable

With this NFR the key question is, how can we test Caching implementation without any actual cache? We can still test this implementation with ReservationCachingTest as shown below

Finally we have covered all aspects of the feature that we were suppose to implement. I do know it has got bit too long, but the nature of topic and relevance it has with chronological order of evolution of implementation does not allow me to convert it into two parts !

Conclusion

As we saw throughout the implementation, Spring Boot has lot of features to easily test Spring Boot application. We can use them as per our need to ensure that we are not only able to have required safety nets whilst implementing them but can also verify / validate design decisions.

Testing is an indispensable practice of software development. I hope this article will be of some help to get you started with Test Driven Development of Spring Boot applications.

Source Code : https://github.com/dhaval201279/bootiful-TDD

2 thoughts on “Bootiful Test Driven Development

  1. Thanks for this nice blog post. I have implemented a couple of end-to-end tests according to your example. These tests influence each other as they modify the data in the database. I thought I could mark the test as @Transactional and all the changes are automatically rollbacked. Unfortunately this did not work. In the end, I put @DirtiesContext on it and it works. Do you know why @Transactional does not work (or what I am misunderstanding)?

    1. If your tests are @Transcational, by default it will rollback entire transaction post execution of test method. If application uses RANDOM_PORT or DEFINED_PORT, then client and server will run in separate thread. And hence transaction initiated on server won’t roll back in that case. Having said that, I personally would not suggest to use @Transactional annotations on Tests for following reasons –

      1. 1. Production code may be using transactions with different scope
      2. 2. It would increase coupling between Tests and production code which would eventually lead us to violation of Contra-variant Tests
      3. 3. One may forget to flush() and it may lead to false positive
      4. 4. In case of failure scenarios and whilst debugging it might become extremely challenging to see the values persisted in DB

Leave a Reply