Skip to main content

Unit and integration test guidelines for Spring Boot

When you have a legacy product that has been around for over 10 years and it is written with old technologies and has no automated tests, how do you improve quality? Sometimes the best solution is to rewrite it!
That was the situation  we were in with a product written with JBOSS. It had become difficult to maintain and debug so we decided to rewrite it using the Spring Boot framework, and add unit and integration tests as we were doing it. This is not a simple undertaking and it's still a work in progress; but I feel we made the right decision.
As not many of the developers had much experience with writing unit and integration tests, I wrote up the guidelines shown below. These have been written for a Java based Spring Boot project that uses the JUnit framework to write the tests, but are general enough to apply to most languages and frameworks.

Guidelines:
  1. Test the behaviour of the class; not its implementation.
  2. Don’t test implementation details; e.g. private methods.
  3. Use descriptive names for your tests that explain what you are testing. Don't be afraid to use long test names. Some examples:
Not very descriptive test case name
More descriptive test case name
saveSysUserRecord()  
newSysUserShouldBeSavedToRepository()
saveBadSysUserRecord()  
newSysUserWithMissingCreateTimeShouldThrowException()
deleteSysUserRecord()
deletingSysUserShouldRemoveItFromRepository()
 
  1. Each test should have the format: Setup test data (Given), do the action you are testing (When), check the result (Then). See example integration test below. Note that you do not need to add in the comments when you actually create the test cases:     
@Test
public void newSysUserShouldBeSavedToRepository() {
//Given
   final long count = repository.count();
       SysUser sysUser = new SysUser();
//When
        repository.saveAndFlush(sysUser);
//Then
       assertThat(repository.count()).isEqualTo(count + 1);
   }
 
  1. Only test one thing per test case. You should only assert the result in the Then section of the code.
  2. Use the AssertJ http://joel-costigliola.github.io/assertj/index.html library to write assertions in a fluid, readable way.
  3. If you need to run a method before each test case (e.g. to setup test data), use the @Before annotation.
  1. Make sure that the test fails if the assert condition is not true. For each test you write change the data to fail the test. This way you are sure that the test will fail if there is a change.  
  2. Test both negative and positive scenarios. For example:
    1. Positive scenario: findByUserIdShouldReturnOneSysUser()
    2. Corresponding negative scenario: findByUserIdWhenUserNonExistentShouldReturnNull()
We found the following talk and corresponding example code by Phil Webb useful:
Video on testing Spring Boot applications:
The source code for the video is in github:
Some tips from the talk:
  • Use AssertJ for assertions. Spring Boot 1.4 and above has it built in.
  • Constructor injection makes it easier to test.
  • Prefer plain Junit when writing unit tests; i.e. try not to involve Spring.
  • When you do need to use Spring (for integration tests) use helper classes such as TestEntity Manager.
  • TestEntityManager is useful to setup test data when testing repositories.


Comments

Popular posts from this blog

Let’s stop writing automated end to end tests through the GUI

What’s the problem? I have not been a fan of Selenium WebDriver since I wrote a set of automated end-to-end tests for a product that had an admittedly complicated user interface. It was quite difficult to write meaningful end-to-end tests and the suite we ended up with was non-deterministic i.e. it failed randomly. Selenium Webdriver may be useful in very simple eCommerce type websites but for most real world products it’s just not up to scratch. This is because it’s prone to race conditions in which Selenium believes that the UI has updated when, in fact, it has not. If this happens, the automated check will fail randomly. While there are techniques for reducing these race conditions, in my experience it is difficult to eradicate them completely. This means that automated checks written with Selenium are inherently flaky or non-deterministic. Maintenance of these automated checks becomes a full time job as it is very time consuming to determine whether a failing check is actuall...

Non Functional Mobile App Testing

When you are testing mobile apps there are a number of non functional elements you need to consider (that do not apply to website testing) such as push notifications, device network issues, location services and app installation. In this post I'll cover these and explain how to test these areas. Push notifications Push notifications were pioneered by Apple in 2008 and this technology was subsequently adopted by Google for its Android OS and by Microsoft for its Windows Phone OS. Push notifications allow an app to deliver information   to a mobile device   without a specific request from the app. This means that the app does not need to be launched for the mobile device to get the push notification. Each operating system has their own Push Notification Service. On iOS it's called Apple Push Notification Service (APNs) and Android had Google Cloud Messaging (GCM) but this has been superseded by Firebase Cloud Messaging (FCM). Note that FCM can also be used to send push n...

How to install a test app onto your mobile device

In this post I'll describe how to actually get test versions of the app onto a device. But first let's discuss whether you should test on an actual device or on an emulator. An emulator is a desktop application that mimics the hardware and OS of a mobile device. The developers will generally do their app development on an emulator and you can use them for early stage testing but when it comes to meaningful end to end testing, a device is a must have. There is no other way to get a feel for the performance of the app and how users will use it real life. Of course you will probably need to test on multiple devices as they vary not only by OS (iOS and Android) but also OS version, device make (e.g. Samsung or Motorola), and screen size. We also now have a new OS for iPads called iPadOS. In a future blog post I'll look at device fragmentation and how to handle it. For now let's assume you are only testing on one iOS device and one Android device. Your developers h...