Mockito 详解(四)MockitoSession

MockitoSession表示一次mock会话,这个会话通常是一次测试方法的执行。

在一个会话周期内,MockitoSession会做三件事:

  1. mock初始化(initializes mocks)
  2. mock使用验证(validates usage)
  3. 检测插桩错误(detects incorrect stubbing)

MockitoSession可以帮我们省去一些原本需要手动去写的代码,并且通过一些额外的验证来驱使我们写“干净的测试”。

MockitoSession结束之后必须调用finishMocking()方法,否则下个Session开始时会抛出异常(UnfinishedMockingSessionException)。

Mockito提供的JUnit组件MockitoJUnitRunnerMockitoRule都使用了MockitoSession,所以如果我们用到了MockitoJUnitRunnerMockitoRule则不需要再使用MockitoSession管理会话。

如果使用了其他单测框架(例如TestNG)或者其他的JUnit Runner(Jukito、Springockito)则需要手动去集成MockitoSession。

先来看一个例子:

public class ExampleTest {
    @Mock Foo foo;

    // Keeping session object in a field so that we can complete session in 'tear down' method.
    // It is recommended to hide the session object, along with 'setup' and 'tear down' methods in a base class / runner.
    // Keep in mind that you can use Mockito's JUnit runner or rule instead of MockitoSession and get the same behavior.
    MockitoSession mockito;

    @Before 
    public void setup() {
        //initialize session to start mocking
        mockito = Mockito.mockitoSession()
            .initMocks(this)
            .strictness(Strictness.STRICT_STUBS)
            .startMocking();
    }

    @After 
    public void tearDown() {
        // It is necessary to finish the session so that Mockito
        // can detect incorrect stubbing and validate Mockito usage
        // 'finishMocking()' is intended to be used in your test framework's 'tear down' method.
        mockito.finishMocking();
    }

    // test methods ...
}

@Before setup方法中通过startMocking启动了一个MockitoSession,相应地,在@After tearDown方法中通过finishMocking结束会话。 ExampleTest中使用Mock标记了一个成员变量——@Mock Foo fooMockitoSession自动创建foo这个mock对象。

Mockito#mockitoSession是一个工厂方法,每次调用都会创建一个新的MockitoSessionBuilder,通过这个Builder再创建一个MockitoSession

mockitoSession的方法命名不太合理,私以为newSessionBuilder更合理一些。

@Incubating
public static MockitoSessionBuilder mockitoSession() {
    return new DefaultMockitoSessionBuilder();
}

MockitoSessionBuilder的定义如下:

@Incubating
public interface MockitoSessionBuilder { 
    @Incubating
    MockitoSessionBuilder initMocks(Object testClassInstance);

    @Incubating
    MockitoSessionBuilder strictness(Strictness strictness);

    @Incubating
    MockitoSession startMocking() throws UnfinishedMockingSessionException;
}

initMocks方法并不会立即初始化标记了@Mock的成员变量,只有调用startMocking创建MockitoSession实例时才会执行初始化。

在一个线程内只允许有一个MockitoSession,所以开启新会话之前必须要调用finishMocking来结束上一次会话。但是多个线程可以允许有多个MockitoSession实例。

finishMocking定义在MockitoSession这个接口中:

@Incubating
public interface MockitoSession {
    @Incubating
    void finishMocking();
}

strictness可以驱动开发人员写“干净的测试”,而且可以根据level来打印日志,方便调试。

@Incubating
public enum Strictness {
    @Incubating
    LENIENT,

    @Incubating
    WARN,

    @Incubating
    STRICT_STUBS
}

Mockito#startMocking方法返回的DefaultMockitoSessionBuilderMockitoSessionBuilder的默认实现:

public class DefaultMockitoSessionBuilder implements MockitoSessionBuilder {
  private Object testClassInstance;
  private Strictness strictness;

  @Override
  public MockitoSessionBuilder initMocks(Object testClassInstance) {
    this.testClassInstance = testClassInstance;
    return this;
  }

  @Override
  public MockitoSessionBuilder strictness(Strictness strictness) {
    this.strictness = strictness;
    return this;
  }

  @Override
  public MockitoSession startMocking() {
    //Configure default values
    Object effectiveTest = this.testClassInstance == null ? new Object() : this.testClassInstance;
    Strictness effectiveStrictness = this.strictness == null ? Strictness.STRICT_STUBS : this.strictness;
    return new DefaultMockitoSession(effectiveTest, effectiveStrictness, new ConsoleMockitoLogger());
  }
}
  • initMocks传入的参数为null时,startMocking会返回一个 new Object()
  • strictness传入的参数为null时,默认使用Strictness.STRICT_STUBS

DefaultMockitoSession#startMocking创建了一个MockitoSession的默认实现——DefaultMockitoSession,我们先来看构造方法:

private final Object testClassInstance;
private final UniversalTestListener listener;

public DefaultMockitoSession(Object testClassInstance, Strictness strictness, MockitoLogger logger) {
    this.testClassInstance = testClassInstance;
    listener = new UniversalTestListener(strictness, logger);
    try {
        //So that the listener can capture mock creation events
        Mockito.framework().addListener(listener);
    } catch (RedundantListenerException e) {
        Reporter.unfinishedMockingSession();
    }
    MockitoAnnotations.initMocks(testClassInstance);
}

构造方法内使用StrictnessMockitoLogger实例创建了一个 UniversalTestListener,它会监听一次mock会话中的事件:

  • onMockCreated
  • testFinished

然后在testFinished事件中,根据Strictness打印日志(借助MockitoLogger)。

switch (currentStrictness) {
    case WARN: emitWarnings(logger, event, createdMocks); break;
    case STRICT_STUBS: reportUnusedStubs(event, createdMocks); break;
    case LENIENT: break;
    default: throw new IllegalStateException("Unknown strictness: " + currentStrictness);
}

MockitoLoggerLogger的接口规范:

public interface MockitoLogger {
    void log(Object what);
}

ConsoleMockitoLogger就是将log打印至System.out

public class ConsoleMockitoLogger implements MockitoLogger {
    public void log(Object what) {
        System.out.println(what);
    }
}

那么UniversalTestListener是如何监听mock的创建呢?

再回到DefaultMockitoSession的构造方法:

Mockito.framework().addListener(listener);

Mockito#framework也是一个工厂方法:

@Incubating
public static MockitoFramework framework() {
    return new DefaultMockitoFramework();
}

MockitoFramework用于管理Mockito框架的配置以及其生命周期的回调。

@Incubating
public interface MockitoFramework {
    @Incubating
    MockitoFramework addListener(MockitoListener listener) throws RedundantListenerException;

    @Incubating
    MockitoFramework removeListener(MockitoListener listener);
}

MockitoFramework支持的Listner类型是MockitoListener,而且同一类型的Listener只能add一次,否则会抛出RedundantListenerException

DefaultMockitoFrameworkMockitoFramework的默认实现:

public class DefaultMockitoFramework implements MockitoFramework {
    public MockitoFramework addListener(MockitoListener listener) {
        Checks.checkNotNull(listener, "listener");
        mockingProgress().addListener(listener);
        return this;
    }

    public MockitoFramework removeListener(MockitoListener listener) {
        Checks.checkNotNull(listener, "listener");
        mockingProgress().removeListener(listener);
        return this;
    }
}

addListenerremoveListener必须在同一个线程内才会生效,因为MockingProgressThreadLocal

public class ThreadSafeMockingProgress {

    private static final ThreadLocal<MockingProgress> MOCKING_PROGRESS_PROVIDER = 
        new ThreadLocal<MockingProgress>() { 
            @Override 
            protected MockingProgress initialValue() {
                return new MockingProgressImpl();
        }
    };

    private ThreadSafeMockingProgress() {
    }

    public final static MockingProgress mockingProgress() {
        return MOCKING_PROGRESS_PROVIDER.get();
    }
}

DefaultMockitoSessionfinishMocking方法会调用removeListener

public void finishMocking() {
    //Cleaning up the state, we no longer need the listener hooked up
    //The listener implements MockCreationListener and at this point
    //we no longer need to listen on mock creation events. We are wrapping up the session
    Mockito.framework().removeListener(listener);

    //Emit test finished event so that validation such as strict stubbing can take place
    listener.testFinished(new TestFinishedEvent() {
        public Throwable getFailure() {
            return null;
        }
        public Object getTestClassInstance() {
            return testClassInstance;
        }
        public String getTestMethodName() {
            return null;
        }
    });

    //Finally, validate user's misuse of Mockito framework.
    Mockito.validateMockitoUsage();
}

Mockito.validateMockitoUsage()也是通过MockingProgress来实现。

MockingProgress顾名思义,表示一次mock过程,实现略复杂,暂且按下不表,后面再分析。

最后再回到DefaultMockitoSession的构造方法:

MockitoAnnotations.initMocks(testClassInstance);

至此则真相大白了,原来@Mock标记的成员变量是由MockitoAnnotations来初始化,欲知后事如何,且看下回分析。

更新时间:

留下评论