Skip to content

Commit

Permalink
Refactoring JUnit rule implementation
Browse files Browse the repository at this point in the history
Needed for work on Mockito strictness, see #769. Decoupled JUnit API from the work Mockito does before and after every test.

No public API exposed, listeners and friends need to be refactored and documented.

Don't merge. This refactoring on its own does not add value.
  • Loading branch information
mockitoguy committed Dec 10, 2016
1 parent 613977a commit 53ea392
Show file tree
Hide file tree
Showing 4 changed files with 117 additions and 55 deletions.
107 changes: 52 additions & 55 deletions src/main/java/org/mockito/internal/junit/JUnitRule.java
Expand Up @@ -7,88 +7,85 @@
import org.junit.runners.model.FrameworkMethod;
import org.junit.runners.model.Statement;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.mockito.internal.util.MockitoLogger;
import org.mockito.junit.MockitoRule;
import org.mockito.listeners.MockCreationListener;
import org.mockito.mock.MockCreationSettings;

import java.util.Collection;
import java.util.LinkedList;
import java.util.List;

/**
* Internal implementation.
*/
public class JUnitRule implements MockitoRule {

private final MockitoLogger logger;
private final boolean silent;
private final Collection<MockitoTestListener> listeners = new LinkedList<MockitoTestListener>();

/**
* @param logger target for the stubbing warnings
* @param silent whether the rule
* @param silent whether the rule emits warnings
*/
public JUnitRule(MockitoLogger logger, boolean silent) {
this.logger = logger;
this.silent = silent;
}

@Override
public Statement apply(final Statement base, FrameworkMethod method, final Object target) {
if (silent) {
return new SilentStatement(target, base);
listeners.add(new SilentTestListener());
} else {
String testName = target.getClass().getSimpleName() + "." + method.getName();
return new DefaultStatement(target, testName, base);
listeners.add(new WarningTestListener(logger));
}
}

public JUnitRule silent() {
return new JUnitRule(logger, true);
}

private class SilentStatement extends Statement {
private final Object target;
private final Statement base;
@Override
public Statement apply(final Statement base, final FrameworkMethod method, final Object target) {
return new Statement() {
public void evaluate() throws Throwable {
MockCollector mockCollector = new MockCollector();
Mockito.framework().addListener(mockCollector);

public SilentStatement(Object target, Statement base) {
this.target = target;
this.base = base;
}
Throwable problem;
try {
for (MockitoTestListener listener : listeners) {
listener.beforeTest(target, method.getName());
}

public void evaluate() throws Throwable {
MockitoAnnotations.initMocks(target);
base.evaluate();
Mockito.validateMockitoUsage();
}
}
problem = evaluateSafely(base);
} finally {
Mockito.framework().removeListener(mockCollector);
}

private class DefaultStatement extends Statement {
private final Object target;
private final String testName;
private final Statement base;
//If the infrastructure fails below, we don't see the original problem, thrown later
for (MockitoTestListener listener : listeners) {
listener.afterTest(mockCollector.mocks, problem);
}

DefaultStatement(Object target, String testName, Statement base) {
this.target = target;
this.testName = testName;
this.base = base;
}

public void evaluate() throws Throwable {
RuleStubbingHintsReporter reporter = new RuleStubbingHintsReporter();
Mockito.framework().addListener(reporter);
try {
performEvaluation(reporter);
} finally {
Mockito.framework().removeListener(reporter);
if (problem != null) {
throw problem;
}
}
}

private void performEvaluation(RuleStubbingHintsReporter reporter) throws Throwable {
MockitoAnnotations.initMocks(target);
try {
base.evaluate();
} catch(Throwable t) {
reporter.getStubbingArgMismatches().format(testName, logger);
throw t;
private Throwable evaluateSafely(Statement base) {
try {
base.evaluate();
return null;
} catch (Throwable throwable) {
return throwable;
}
}
reporter.getUnusedStubbings().format(testName, logger);
Mockito.validateMockitoUsage();
};
}

public JUnitRule silent() {
return new JUnitRule(logger, true);
}

private static class MockCollector implements MockCreationListener {
private final List<Object> mocks = new LinkedList<Object>();

public void onMockCreated(Object mock, MockCreationSettings settings) {
mocks.add(mock);
}
}

}
10 changes: 10 additions & 0 deletions src/main/java/org/mockito/internal/junit/MockitoTestListener.java
@@ -0,0 +1,10 @@
package org.mockito.internal.junit;

import java.util.Collection;

interface MockitoTestListener {

void beforeTest(Object testClassInstance, String testMethodName);

void afterTest(Collection<Object> mocks, Throwable problem);
}
19 changes: 19 additions & 0 deletions src/main/java/org/mockito/internal/junit/SilentTestListener.java
@@ -0,0 +1,19 @@
package org.mockito.internal.junit;

import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;

import java.util.Collection;

class SilentTestListener implements MockitoTestListener {
public void beforeTest(Object testClassInstance, String testMethodName) {
MockitoAnnotations.initMocks(testClassInstance);
}

public void afterTest(Collection<Object> mocks, Throwable problem) {
if (problem == null) {
//Validate only when there is no other problem to avoid reporting multiple problems
Mockito.validateMockitoUsage();
}
}
}
36 changes: 36 additions & 0 deletions src/main/java/org/mockito/internal/junit/WarningTestListener.java
@@ -0,0 +1,36 @@
package org.mockito.internal.junit;

import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.mockito.internal.util.MockitoLogger;

import java.util.Collection;

class WarningTestListener implements MockitoTestListener {

private final MockitoLogger logger;
private String testName;

WarningTestListener(MockitoLogger logger) {
this.logger = logger;
}

public void beforeTest(Object testClassInstance, String testMethodName) {
testName = testClassInstance.getClass().getSimpleName() + "." + testMethodName;
MockitoAnnotations.initMocks(testClassInstance);
}

public void afterTest(Collection<Object> mocks, Throwable problem) {
if (problem != null) {
//print stubbing mismatches only when there is a test failure
//to avoid false negatives. Give hint only when test fails.
new ArgMismatchFinder().getStubbingArgMismatches(mocks).format(testName, logger);
} else {
//Validate only when there is no other problem to avoid reporting multiple problems
Mockito.validateMockitoUsage();

//print unused stubbings only when test succeeds to avoid reporting multiple problems and confusing users
new UnusedStubbingsFinder().getUnusedStubbings(mocks).format(testName, logger);
}
}
}

0 comments on commit 53ea392

Please sign in to comment.