logo
Solutionunittest

BDD Mockito

Views: 262 Created: 2019-02-04 Read time: 3 minutes
Post Preview
Tags:

1)     Preface

BDD has become more and more popular in IT teams as it introduces simple and clear language constructs to express behaviour and expected outcome. It is also a popular choice for expressing user stories and requirements between technical and business people. If BDD is the standard of communication in your company, Mockito also gives you a choice to take advantage of this design in your automated testing suite. Let's get a grab of how this works.  

 

2)     Mockito BDD: it shall be given to you

Let us try to go through a basic example where we will try to replace the standard when().then() syntax with a BDD stylisation. This time we will go through our Smart Home application where we would like to turn on the movement sensors for a given time:

 

SUT Code


public List<LocalDateTime> recordMovement(Integer minutes){
    List<LocalDateTime> movements = new ArrayList<>();

    securityDriver.startMovementRecording(minutes);

    while(!securityDriver.shouldStopRecording(LocalDateTime.now())){
        try {
            securityDriver.checkForMovement();
        } catch (MovementSpottedExcpetion e){
		    movements.add(LocalDateTime.now());
            auditService.logMovement(LocalDateTime.now());
        }
    }

    return movements;
}

In this SUT method, we are:

      (L5):  Turning on movement detection.

      (L7):  Checking whether a time has been reached where we should stop the recording.

      (L9):  Check whether any movement has been observed. Normally nothing should happen.

      (L11):  When a movement occurs, an exception is thrown and the exact time is being saved/logged.

 

Now let us try to write a test where no movement is observed and no times should be recorded:

 

Test Code


@Mock private SecurityDriver securityDriverMock;
@InjectMocks private RemoteControlController remoteControlControllerSUT;

@Test
public void shouldNotRecordAnyMovement() throws Exception{
    // Given
    given(securityDriverMock.shouldStopRecording(any(LocalDateTime.class)))
            .willReturn(false)
            .willReturn(false)
            .willReturn(true);

    // When
    List<LocalDateTime> recordedMovement = remoteControlControllerSUT.recordMovement(1);

    // Then
    assertThat(recordedMovement).isEmpty();
}

In this test method, we are:

      (L8):  Preparing the securityDriverMock.shouldStopRecording method, that when any LocalDateTime object is passed, for the first two invocations it will return false, to finally return true on the third attempt.

      (L14):  By default securityDriver.checkForMovement() does not record anything, and we will keep this invocation this way by not stubbing it.

      (L17):  Making sure that nothing has been recorded.

 

Note
Note

 

3)     Mockito BDD: verify particular behaviour

Apart from being able to check the actual state with assertThat methods, a lot of times we would also like to make sure that particular behaviour was recorded on one of our mocks. Let's try to verify functioning the BDD way. Let us go back to the previous example, but now we will want to record that a movement has happened.

 

Test Code


@Mock private AuditService auditServiceMock;
@Mock private SecurityDriver securityDriverMock;
@InjectMocks private RemoteControlController remoteControlControllerSUT;

@Test
public void shouldRecordMovement() throws Exception{
    // Given
    given(securityDriverMock.shouldStopRecording(any(LocalDateTime.class)))
            .willReturn(false)
            .willReturn(false)
            .willReturn(true);

    willThrow(new MovementSpottedExcpetion())
            .given(securityDriverMock).checkForMovement();

    // When
    List<LocalDateTime> recordedMovement = remoteControlControllerSUT.recordMovement(1);

    // Then
    assertThat(recordedMovement).hasSize(2);

    then(auditServiceMock).should(times(2))
            .logMovement(any(LocalDateTime.class));
}

In this test method, we are:

      (L9):  Setting up the securityDriverMock.shouldStopRecording as before.

      (L14):  This time we are making sure that each time securityDriverMock.checkForMovement method is called an actual movement has been observed.

      (L21):  Making sure that two movement timestamps have been returned.

      (L23):  Making sure that the system has logged the movement accordingly.

 

Note
Note

 

4)     Mockito BDD: spying on methods

Sometimes instead of a mock, you will need to work with a spy of an actual object. In most cases, we need to use a Spy on the SUT object itself to stub some of its own method call which may be a bit too complex and unwanted in our simple test scenarios.

 

Note
Note

 

SUT Code


public HouseStats generateHouseStats(){
    SecurityStats securityStats = securityDriver.getLastMonthStats();
    RobotStats robotStats = robotCleanerDriver.getLastMonthStats();
    HeatingStats heatingStats = heatingDriver.getLastMonthStats();
    LightningStats lightningStats = lightningDriver.getLastMonthStats();

    return assembleHouseStats(securityStats, robotStats, heatingStats, lightningStats);
}

protected HouseStats assembleHouseStats(SecurityStats securityStats
        , RobotStats robotStats
        , HeatingStats heatingStats
        , LightningStats lightningStats) {

    /* Complex task involving many dependencies */

}

In this SUT method, we are:

      (L2):  Generating a full house report by collecting stats from each of the drivers.

      (L8):  We call another SUT method to assemble the report having all those stats given. This is a complex implementation, and we do not really want to call it directly here.

 

Let us try to write a test that invokes the generateHouseStats method and only makes sure that all the drivers have been called and their stats passed to the assembleHouseStats.

 

Test Code


@Spy
@InjectMocks
private RemoteControlController remoteControlControllerSpy;

@Test
public void shouldGenerateHouseReport() throws Exception{
    // Given
    SecurityStats securityStats = new SecurityStats();
    RobotStats robotStats = new RobotStats();
    HeatingStats heatingStats = new HeatingStats();
    LightningStats lightningStats = new LightningStats();

    given(securityDriverMock.getLastMonthStats()).willReturn(securityStats);
    given(lightningDriverMock.getLastMonthStats()).willReturn(lightningStats);
    given(heatingDriverMock.getLastMonthStats()).willReturn(heatingStats);
    given(robotCleanerDriverMock.getLastMonthStats()).willReturn(robotStats);

    willReturn(new HouseStats()).given(remoteControlControllerSpy).assembleHouseStats(
            securityStats, robotStats, heatingStats, lightningStats);

    // When
    HouseStats houseStats = remoteControlControllerSpy.generateHouseReport();

    // Then
    then(heatingDriverMock).should().getLastMonthStats();
    then(lightningDriverMock).should().getLastMonthStats();
    then(robotCleanerDriverMock).should().getLastMonthStats();
    then(securityDriverMock).should().getLastMonthStats();

    assertThat(houseStats).isNotNull();
}

In this test method, we are:

      (L14-17):  Making sure that each of the drivers returns adequate stats.

      (L19):  Stubbing the assembleHouseStats of the SUT to avoid calling real implementation. Take note here that we start with willReturn, so kind of another way around than usual. This is necessary in case of spies. Were we to begin with given, the real implementation would have been called before Mockito would get a chance to wrap all up with a proxy and prevent it from happening.

      (L26-29):  Verifying all the drivers have provided their stats.

 

 

5)     Conclusion

 

Summary

 

We have seen examples of how the BDD syntax can easily replace the standard Mockito syntax and having the same outcome. It is up to you to decide which style to take advantage of. Just make sure you and your team are consistent and that both forms are not mixed in your automation suite. Choose how you want to behave and stick to it!

Need more insight?
Repository
Repository
Glossary
Glossary
Tags:
Reference
You may also like:
test-method-naming
Test method naming
automated-testing-pyramid
Automated testing pyramid
Comments
Be the first to comment.
Leave a comment