Mockito 详解(四)MockitoSession

Posted by liangfei on 2017-07-25

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来初始化,欲知后事如何,且看下回分析。


欢迎扫码关注 老梁写代码 微信公众号