vendredi 17 janvier 2014

Unit tests and integration tests coverage in sonar with gradle

I recently needed to configure a multi-module gradle project in order to generate unit test, integration test and overall coverage indicators in sonar. As it wasn't so obvious I thought it would be a good idea to share my example.

The goal is to generate the following report in sonar :


Complete gradle file


So here is my complete build.gradle file :

apply plugin: 'sonar-runner'

allprojects {
  apply plugin: 'java'
  apply plugin: 'jacoco'

  sourceSets {
    integtest {
      java {
        compileClasspath += main.output
        runtimeClasspath += main.output
        srcDir 'src/integtest/java'
      }
    }
  }

  configurations {
    integtestCompile.extendsFrom testCompile
  }

  task "integtest"(type: Test, dependsOn: integtestClasses) {
    testClassesDir = sourceSets.integtest.output.classesDir
    classpath = sourceSets.integtest.runtimeClasspath

      jacoco {
          destinationFile = file("$buildDir/jacoco/jacocoIntegTest.exec")
      }
  }

  test {
    jacoco {
      destinationFile = file("$buildDir/jacoco/jacocoTest.exec")
    }
  }

  sonarRunner {
    sonarProperties {
      property "sonar.jacoco.reportPath", "$buildDir/jacoco/jacocoTest.exec"
      property "sonar.jacoco.itReportPath", "$buildDir/jacoco/jacocoIntegTest.exec"
    }
  }
}

sonarRunner {
    sonarProperties {
        property "sonar.projectName", "test-samples"
        property "sonar.projectKey", "jsebfranck.samples"
    }
}

Explanations

I used Jacoco to generate my tests coverage indicators :

  apply plugin: 'jacoco'


The following lines allow to configure my integration tests. To distinguish these tests from the unit tests, I moved them in a specific source folder : "src/integtest/java". Please note that with gradle, the default test folder is "src/test/java" and I use it for my unit tests.

  sourceSets {
    integtest {
      java {
        compileClasspath += main.output
        runtimeClasspath += main.output
        srcDir 'src/integtest/java'
      }
    }
  }

  configurations {
    integtestCompile.extendsFrom testCompile
  }

  task "integtest"(type: Test, dependsOn: integtestClasses) {
    testClassesDir = sourceSets.integtest.output.classesDir
    classpath = sourceSets.integtest.runtimeClasspath
  }

I configured Jacoco to generate the tests coverage data in a specific directory : one for the unit tests and for the integration tests :

  task "integtest"(type: Test, dependsOn: integtestClasses) {
    testClassesDir = sourceSets.integtest.output.classesDir
    classpath = sourceSets.integtest.runtimeClasspath

      jacoco {
          destinationFile = file("$buildDir/jacoco/jacocoIntegTest.exec")
      }
  }

  test {
    jacoco {
      destinationFile = file("$buildDir/jacoco/jacocoTest.exec")
    }
  }

Then for each module, I configured sonar to use generated Jacoco data :
  • reportPath property allows to configure the unit tests coverage
  • itReportPath property allows to configure the integration tests coverage
allprojects {
  sonarRunner {
    sonarProperties {
      property "sonar.jacoco.reportPath", "$buildDir/jacoco/jacocoTest.exec"
      property "sonar.jacoco.itReportPath", "$buildDir/jacoco/jacocoIntegTest.exec"
    }
  }
}

Finally I configured my sonar instance. Here I use the default parameters so the report is generated on my localhost sonar installation and its h2 database :

sonarRunner {
    sonarProperties {
        property "sonar.projectName", "test-samples"
        property "sonar.projectKey", "jsebfranck.samples"
    }
}


Source code

Please find the complete source code on my github repository about tests.



dimanche 5 janvier 2014

Mockito in the trenches

I used EasyMock framework for three years, before starting to use Mockito two years ago and I'm now really happy with this framework.

However, it provides some features I never used, and for some features it provides several ways to write your tests. For example you have two ways to create your mocks and two ways to write your expectations.

So, in this article I will present every feature I use, and how I choose to write my Mockito tests.

Our example

Let's start with a simple example, then we will go deeper into the framework.

The class we want to test is a class managing accounts, AccountService, with a single dependency, its repository : AccountRepository. For the moment, AccountService has only one method :

public class AccountService {
  @Inject
  private AccountRepository accountRepository;

  public Account getAccountByLogin(String login) {
    try {
      return accountRepository.findAccount(login);
    } catch (EntityNotFoundException enfe) {
      return null;
    }
  }
}

How can we test that with Mockito?

Mocks creation


The first thing you have to do is to create your class of test and create your mocks. I recommend to use the Mockito annotations and the Junit runner as it is more elegant and concise :

@RunWith(MockitoJUnitRunner.class)
public class AccountServiceTest {
  @InjectMocks
  private AccountService accountService;

  @Mock
  private AccountRepository accountRepository;
}

As you can see, you don't need a @Before method and you don't have to instantiate your objects. All is done automatically by the framework. To inject the mocks in the class to test, Mockito tries to do that using firstly the object constructor, then the object setters or finally the object properties.

doReturn and verify


Now let's test the method getAccountByLogin and the case where an account already exists.

  private static final String LOGIN = "login";
  private static final String PASSWORD = "password";

  @Test
  public void getAccountByLogin_withExistingAccount_shouldReturnTheAccount() 
    throws Exception {
    // Given
    Account existingAccount = new Account(LOGIN, PASSWORD);
    doReturn(existingAccount).when(accountRepository).findAccount(LOGIN);

    // When
    Account result = accountService.getAccountByLogin(LOGIN);

    // Then
    assertEquals(existingAccount, result);
    verify(accountRepository).findAccount(LOGIN);
    verifyNoMoreInteractions(accountRepository);
  }

Explanations :

  • doReturn allows to override the behavior of the AccountRepository.findAccount method. Here we return an Account object to simulate the case that an account already exists
  • verify(accountRepository).findAccount allows to check that this method has been called
  • verifyNoMoreInteractions method allows to be sure that we didn't call another method on this mock. It is useful in that case because you test that you don't do a useless call on your repository

doThrow

Now let's test the case where the account doesn't exist. In that case the repository throws an EntityNotFoundException :

  @Test
  public void getAccountByLogin_withUnexistingAccount_shouldReturnNull() throws Exception {
    // Given
    doThrow(new EntityNotFoundException()).when(accountRepository).findAccount(LOGIN);

    // When
    Account result = accountService.getAccountByLogin(LOGIN);

    // Then
    assertNull(result);
  }

As we used doReturn before, here we use doThrow method. Easy!

doReturn vs thenReturn, doThrow vs thenThrow

With mockito the following instructions are similar :

  // either :
  doReturn(null).when(accountService).getAccountByLogin(LOGIN);
  // or :  
  when(accountService.getAccountByLogin(LOGIN)).thenReturn(null);

  // either :
  doThrow(new EntityNotFoundException()).when(accountRepository).findAccount(LOGIN);
  // or :
  when(accountRepository.findAccount(LOGIN)).thenThrow(new EntityNotFoundException());

However, in the case you want to test a void method, you can write :

  doThrow(new EntityNotFoundException()).when(accountRepository).deleteAccount(LOGIN);

But you cannot write the following instruction because it doesn't compile :

  when(accountRepository.deleteAccount(LOGIN)).thenThrow(new EntityNotFoundException());

So I recommend to always use the do* methods in order to keep your tests coherent.

Object.equals and argument captor


Now let's add another method which create an account :

  public void createAccount(String login, String password) throws ServiceException {
    Account account = new Account(login, password);
    accountRepository.createAccount(account);
  }

It seems very easy to write this test. Let's do a first try :

  @Test
  public void createAccount_nominalCase_shouldCreateTheAccount() throws Exception {
    // When
    accountService.createAccount(LOGIN, PASSWORD);

    // Then
    verify(accountRepository).createAccount(new Account(LOGIN, PASSWORD));   
    verifyNoMoreInteractions(accountRepository);
  }

But unfortunately the test fails! Why? Because the verify instruction checks that the parameters passed in the createAccount method are the same in the test that in the tested code. However, in that case, the Account object has no equals method.

You may think to add an equals method in the Account object but if you do that only for your unit test, it is really a shame. Hopefully Mockito provides a cleaner way to test this case : the argument captor.

  @Test
  public void createAccount_nominalCase_shouldCreateTheAccount() throws Exception {
    // When
    accountService.createAccount(LOGIN, PASSWORD);

    // Then
    assertThatAccountAsBeenCreated();
    verifyNoMoreInteractions(accountRepository);
  }
  
  private void assertThatAccountAsBeenCreated() {
    ArgumentCaptor argument = ArgumentCaptor.forClass(Account.class);
    verify(accountRepository).createAccount(argument.capture());
    Account createdAccount = argument.getValue();

    assertEquals(LOGIN, createdAccount.getLogin());
    assertEquals(PASSWORD, createdAccount.getPassword());    
  }

With this technic, you can retrieve the object passed in the tested code, then do every assertion you want. Here we just want to be sure that the login and the password are correct.

Partial mocking

Now let's add three lines of code in our createAccount method to check that the account doesn't exist before we create it. To do that, we use the existing method getAccountByLogin :

  public void createAccount(String login, String password) throws ServiceException {
    if (getAccountByLogin(login) != null) {
     throw new ServiceException(String.format("The account %s already exists", login));
    }

    Account account = new Account(login, password);
    accountRepository.createAccount(account);
  }

As you have already tested the getAccountByLogin, you don't want to test it again because our tests could become unmaintainable.

So here you can use the partial mocking. The idea is to mock the call to the getAccountByLogin method within the class you are testing. With Mockito (unlike other mock frameworks) it is very easy to do that. You just have to override your method behavior like if you were using a mock :

  @Test
  public void createAccount_nominalCase_shouldCreateTheAccount() throws Exception {
    // Given
    doReturn(null).when(accountService).getAccountByLogin(LOGIN);

    // When
    accountService.createAccount(LOGIN, PASSWORD);

    // Then
    assertThatAccountAsBeenCreated();
    verifyNoMoreInteractions(accountRepository);
    verify(accountService).getAccountByLogin(LOGIN);
  }

Then you just have to add the @Spy annotation on the declaration of your tested object :

  @InjectMocks
  @Spy
  private AccountService accountService;

And it works!

Conclusion

I think I covered every feature of Mockito I use every day. Don't hesitate to ping me if I forgot something important. Anyway, I hope this tutorial helped you to have a better understanding of this great framework!

The whole code, with more cases and tests, is available on my github account.

Also, don't hesitate to consult my recommendations to write maintainable unit tests.

Cheers!