Thursday, 6 June 2013

BDD: Testing absence of behaviour in Mockito via a custom Mock Controller

One thing I really miss with Mockito is the use of a Mock Controller (such as with EasyMock) to collect and control mocks particularly in testing for the absence of behaviour.

To illustrate lets look at the following example:

public class ServiceImpl implements Service {

    @Autowired
    private ServiceDao serviceDao;

    public void doSomething(String value){
        serviceDao.doSomething(value);
    }
}

 
public class ServiceImplTest {

    @InjectMocks
    private ServiceImpl serviceImpl;

    @Mock
    private ServiceDao serviceDaoMock;

    @BeforeMethod
    public void setUp(){
        serviceImpl = new ServiceImpl();
        MockitoAnnotations.init(this);
    }

    @Test
    public void shouldServiceDoSomething(){
        serviceImpl.doSomething("value");
        verify(serviceDaoMock).doesSomething("value");
        MockitoAnnotations.verifyNoMoreInteractions(serviceDaoMock);
    }
}

Which looks okay on first inspection.

You can from the 'shouldServiceDoSomething' test that the mock does something and that there is no more behaviour on it. But what happens when say another developer adds another service which effects 'doSomething' but misses the test?

 To illustrate:

public class ServiceImpl implements Service {

    @Autowired
    private ServiceDao serviceDao;

    @Autowired
    private AnotherServiceDao anotherServiceDao;

    public void doSomething(String value){
        serviceDao.doSomething(value);
        anotherServiceDao.doSomething(value);
    }

    public void doSomethingElse(String value){
        anotherServiceDao.doSomething(value);
    }
}
 
public class ServiceImplTest {

    @InjectMocks
    private ServiceImpl serviceImpl;

    @Mock
    private ServiceDao serviceDaoMock;

    @Mock
    private AnotherServiceDao anotherServiceDaoMock;

    @BeforeMethod
    public void setUp(){
        serviceImpl = new ServiceImpl();
        MockitoAnnotations.init(this);
    }

    @Test
    public void shouldServiceDoSomething(){
        serviceImpl.doSomething("value");
        verify(serviceDaoMock).doesSomething("value");
        // Behaviour changed but will not break ;-(
        MockitoAnnotations.verifyNoMoreInteractions(serviceDaoMock);
    }

    @Test
    public void shouldServiceDoSomethingElse(){
        serviceImpl.doSomethingElse("value");
        verify(anotherServiceDaoMock).doesSomething("value");
        MockitoAnnotations.verifyNoMoreInteractions(anotherServiceDaoMock);
    }
}

Do you see the problem? 

 Both tests pass, yet the true behaviour of the method 'doSomething' is no longer tested by 'shouldServiceDoSomething' And the developers are none the wiser to this expectation as nothing breaks.

The developers are forced to update the tests for 'verifyNoMoreInteractions' passing in all mocks for the test to fail, which is great in theory but not practical. 

This is where a MockitoAnnotationsExtension does it's work. 

To illustrate:

public class ServiceImplTest {

    @InjectMocks
    private ServiceImpl serviceImpl;

    @Mock
    private ServiceDao serviceDaoMock;

    @Mock
    private AnotherServiceDao anotherServiceDaoMock;

    @BeforeMethod
    public void setUp(){
        serviceImpl = new ServiceImpl();
        MockitoAnnotationsExtension.init(this);
    }

    @Test
    public void shouldServiceDoSomething(){
        serviceImpl.doSomething("value");
        verify(serviceDaoMock).doesSomething("value");
        // Will break now :-)
        MockitoAnnotationsExtension.verifyNoMoreInteractions();
    }

    @Test public void shouldServiceDoSomethingElse(){
        serviceImpl.doSomethingElse("value");
        verify(anotherServiceDaoMock).doesSomething("value");
        MockitoAnnotationsExtension.verifyNoMoreInteractions();
    }
}

In which case we have complete control over all the mocks and the test 'shouldServiceDoSomething' will break to indicate to us that there has been a change in the expected behaviour of the method. 

 Which is exactly what we want! 

 To illustrate a MockitoAnnotationsExtension:

package com.foo.test;

import org.mockito.InOrder;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.mockito.MockitoDebugger;
import org.mockito.internal.debugging.MockitoDebuggerImpl;

import java.lang.reflect.Field;
import java.util.HashSet;
import java.util.Set;

import static org.apache.commons.lang.Validate.notNull;

/**
* An extension which allows for assertions over all Mockito mocks defined in a test class
* In this way behaviour against ALL test class defined mocks (@Mock) can be asserted.
*
* @see org.mockito.MockitoAnnotations
* @see org.mockito.InOrder
* @see org.mockito.Mockito
*/
public final class MockitoAnnotationsExtension {

    private MockitoAnnotationsExtension(){} // NOSONAR

    private static final MockitoDebugger mockitoDebugger = new MockitoDebuggerImpl();

    private static Object[] allAnnotatedMocksCapture = null;

    public static void initMocksExtension(final Object testClass){
        MockitoAnnotations.initMocks(testClass);
        allAnnotatedMocksCapture = getAllAnnotatedMocks(testClass);
    }

    public static void tearDown(){
        allAnnotatedMocksCapture = null;
    }

    /**
    * Wrapper for Mockito verifyNoMoreInteractions
    * @see org.mockito.Mockito#verifyNoMoreInteractions(Object...)
    */
    public static void verifyNoMoreInteractionsExtension() {
        validateMethodUse();
        Mockito.verifyNoMoreInteractions(allAnnotatedMocksCapture);
    }

    /**
    * Wrapper for Mockito verifyZeroInteractions
    * @see org.mockito.Mockito#verifyZeroInteractions(Object...)
    */
    public static void verifyZeroInteractionsExtension() {
        validateMethodUse();
        Mockito.verifyZeroInteractions(allAnnotatedMocksCapture);
    }

    /**
    * Wrapper for Mockito reset
    * @see org.mockito.Mockito#reset(Object[])
    */
    public static void resetExtension() {
        validateMethodUse();
        Mockito.reset(allAnnotatedMocksCapture);
    }

    /**
    * Wrapper for Mockito inOrder
    * @see org.mockito.Mockito#inOrder(Object...)
    */
    public static InOrder inOrderExtension() {
        validateMethodUse();
        return Mockito.inOrder(allAnnotatedMocksCapture);
    }

    /**
    * Wrapper for MockitoDebuggerImpl printInvocations
    * @see org.mockito.MockitoDebugger#printInvocations(Object...)
    */
    public static void debugPrintInvocationsExtension(){
        validateMethodUse();
        mockitoDebugger.printInvocations(allAnnotatedMocksCapture);
    }

    private static void validateMethodUse(){
        notNull(allAnnotatedMocksCapture, "MockitoAnnotationsExtension.initMocksExtension must be called prior to     this method call");
    }

    private static boolean isInjectedMock(Field field){
        return field.getAnnotation(org.mockito.Mock.class) != null;
    }

    static Object[] getAllAnnotatedMocks(final Object testClass) {
        try{
            Set <Object> mocks = new HashSet<Object>();
            for (Field field : testClass.getClass().getDeclaredFields()) {
                if (isInjectedMock(field)){
                    field.setAccessible(true);
                    mocks.add(field.get(testClass));
                }
            }
            return mocks.toArray();
        }catch (Exception unexpectedWrapped){
            throw new RuntimeException(unexpectedWrapped);
        }
    }
}

I was in discussions with Mockito over the use of the control API and it might be a feature in future. But until then this is a work around!

Tuesday, 4 June 2013

BDD: Running Twist Features Inside Unit Tests

The following code is a work around to support running Twist features within Unit Test through any IDE.

It's quite useful when debugging scenarios and you've just had enough of the Eclipse Plugin or would just prefer to use something else! :-)

I hope it proves useful to other Twist users out there.

package com.test.twist.runner;

import com.thoughtworks.twist.core.execution.ant.ScenarioExecutorAntMain;
import org.springframework.test.util.ReflectionTestUtils;

import java.io.File;
import java.util.HashMap;
import java.util.Map;

import static com.thoughtworks.twist.core.execution.ant.ScenarioExecutorAntMain.*;

public abstract class AbstractTwistScenarioRunner {

    private static final String PROJECT_FOLDER = "integrationtest";

    enum TwistScenarioResult{

        PASS(PASS_EXIT_CODE),
        FAILURE(FAILURE_EXIT_CODE),
        ERROR(ERROR_EXIT_CODE),
        FAILURE_AND_ERROR(FAILURE_AND_ERROR_EXIT_CODE),
        UNKNOWN(-1);

        private int exitCode;

        TwistScenarioResult(int exitCode){
            this.exitCode = exitCode;
        }

        public static TwistScenarioResult fromExitCode(int exitCode){
            for(TwistScenarioResult result : TwistScenarioResult.values()){
                if (result.exitCode == exitCode){
                    return result;
                }
            }
            return UNKNOWN;
        }
    }

    protected ScenarioExecutorAntMain initExecutor(String reportsDirectory, String configurationDirectory, String         scenariosDirectory){
        Map parameters = new HashMap(6);
        parameters.put("reportsDir", new File(PROJECT_FOLDER + reportsDirectory));
        parameters.put("confDir", new File(PROJECT_FOLDER + configurationDirectory));
        parameters.put("scenarioDir", new File(PROJECT_FOLDER + scenariosDirectory));
        parameters.put("tags", "ignored");
        parameters.put("numberOfThreads", 1);
        parameters.put("scenariosListFile", null);
        return new ScenarioExecutorAntMain(parameters);
    }

    protected TwistScenarioResult executeScenarios(ScenarioExecutorAntMain executor, String tags){
    
    ReflectionTestUtils.setField(executor, "tags", tags);
        ScenarioExecutorAntMain.ExitCodeListener exitCodeListener = executor.start();
        int twistExitCode = executor.exitCode(exitCodeListener);
            return TwistScenarioResult.fromExitCode(twistExitCode);
        }
}

And the test implementation:

package com.test.twist.runner;

import com.thoughtworks.twist.core.execution.ant.ScenarioExecutorAntMain;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

import static org.fest.assertions.Assertions.assertThat;

/**
* Allows Twist Tests to be run inside Intellij and makes use of Intellij debugging!
*/
public class TwistScenarioRunner extends AbstractTwistScenarioRunner {

  private ScenarioExecutorAntMain scenarioExecutor;

  @BeforeMethod
  public void setUp(){
      String reportsDirectory = "/target/surefire-reports";
      String configurationDirectory = "/twist-conf";
      String scenariosDirectory = "/scenarios";
      scenarioExecutor = initExecutor(reportsDirectory, configurationDirectory, scenariosDirectory);
  }

  @Test
  public void shouldExecuteSpecificScenarios() throws Exception {
      assertThat(executeScenarios(scenarioExecutor, "my-scenario,!in-progress,!ignore")).as("My Scenarios").isEqualTo(TwistScenarioResult.PASS);
  }

}