Skip to content

Commit

Permalink
Made the JUnit runner thread safe
Browse files Browse the repository at this point in the history
 - unused stubbing detection logic was not thread safe in Mockito JUnit runner
 - stopped using the StubbingListener - by design it couldn't support thread safe scenarios unfortunately :(

Fixes #384
  • Loading branch information
mockitoguy committed Aug 19, 2016
1 parent 61a9bff commit c0a67d7
Show file tree
Hide file tree
Showing 8 changed files with 203 additions and 160 deletions.
@@ -0,0 +1,38 @@
package org.mockito.internal.junit;

import org.junit.runner.Description;
import org.junit.runner.notification.Failure;
import org.junit.runner.notification.RunNotifier;
import org.mockito.internal.exceptions.Reporter;
import org.mockito.invocation.Invocation;
import org.mockito.listeners.MockCreationListener;
import org.mockito.mock.MockCreationSettings;

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

/**
* Reports unnecessary stubbings
*/
public class UnnecessaryStubbingsReporter implements MockCreationListener {

private List<Object> mocks = new LinkedList<Object>();

public void validateUnusedStubs(Class<?> testClass, RunNotifier notifier) {
Collection<Invocation> unused = new UnusedStubbingsFinder().getUnusedStubbingsByLocation(mocks);
if (unused.size() == 0) {
return; //whoa!!! All stubbings were used!
}

//Oups, there are unused stubbings
Description unnecessaryStubbings = Description.createTestDescription(testClass, "unnecessary Mockito stubbings");
notifier.fireTestFailure(new Failure(unnecessaryStubbings,
Reporter.formatUnncessaryStubbingException(testClass, unused)));
}

@Override
public void onMockCreated(Object mock, MockCreationSettings settings) {
mocks.add(mock);
}
}
4 changes: 2 additions & 2 deletions src/main/java/org/mockito/internal/junit/UnusedStubbings.java
Expand Up @@ -8,7 +8,7 @@
/**
* Contains unused stubbings, knows how to format them
*/
class UnusedStubbings {
public class UnusedStubbings {

private final Collection<Stubbing> unused;

Expand All @@ -32,7 +32,7 @@ void format(String testName, MockitoLogger logger) {
logger.log(hint.toString());
}

int size() {
public int size() {
return unused.size();
}

Expand Down
@@ -1,17 +1,17 @@
package org.mockito.internal.junit;

import org.mockito.internal.invocation.Stubbing;
import org.mockito.internal.invocation.finder.AllInvocationsFinder;
import org.mockito.internal.util.collections.ListUtil;
import org.mockito.internal.invocation.Stubbing;
import org.mockito.invocation.Invocation;

import java.util.Collection;
import java.util.LinkedList;
import java.util.*;

/**
* Finds unused stubbings
*/
class UnusedStubbingsFinder {
UnusedStubbings getUnusedStubbings(Iterable<Object> mocks) {
public class UnusedStubbingsFinder {
public UnusedStubbings getUnusedStubbings(Iterable<Object> mocks) {
Collection<Stubbing> stubbings = (Collection) AllInvocationsFinder.findStubbings(mocks);

LinkedList<Stubbing> unused = ListUtil.filter(stubbings, new ListUtil.Filter<Stubbing>() {
Expand All @@ -22,4 +22,32 @@ public boolean isOut(Stubbing s) {

return new UnusedStubbings(unused);
}

public Collection<Invocation> getUnusedStubbingsByLocation(Iterable<Object> mocks) {
Collection<Stubbing> stubbings = (Collection) AllInvocationsFinder.findStubbings(mocks);

//1st pass, collect all the locations of the stubbings that were used
//note that those are _not_ locations where the stubbings was used
Set<String> locationsOfUsedStubbings = new HashSet<String>();
for (Stubbing s : stubbings) {
if (s.wasUsed()) {
String location = s.getInvocation().getLocation().toString();
locationsOfUsedStubbings.add(location);
}
}

//2nd pass, collect unused stubbings by location
//If the location matches we assume the stubbing was used in at least one test method
//Also, using map to deduplicate reported unused stubbings
// if unused stubbing appear in the setup method / constructor we don't want to report it per each test case
Map<String, Invocation> out = new LinkedHashMap<String, Invocation>();
for (Stubbing s : stubbings) {
String location = s.getInvocation().getLocation().toString();
if (!locationsOfUsedStubbings.contains(location)) {
out.put(location, s.getInvocation());
}
}

return out.values();
}
}
5 changes: 3 additions & 2 deletions src/main/java/org/mockito/internal/runners/StrictRunner.java
Expand Up @@ -5,6 +5,7 @@
import org.junit.runner.manipulation.NoTestsRemainException;
import org.junit.runner.notification.RunNotifier;
import org.mockito.Mockito;
import org.mockito.internal.junit.UnnecessaryStubbingsReporter;
import org.mockito.internal.runners.util.FailureDetecter;

public class StrictRunner implements RunnerImpl {
Expand All @@ -27,13 +28,13 @@ public void run(RunNotifier notifier) {
UnnecessaryStubbingsReporter reporter = new UnnecessaryStubbingsReporter();
FailureDetecter listener = new FailureDetecter();

Mockito.framework().setStubbingListener(reporter);
Mockito.framework().addListener(reporter);
try {
// add listener that detects test failures
notifier.addListener(listener);
runner.run(notifier);
} finally {
Mockito.framework().setStubbingListener(null);
Mockito.framework().removeListener(reporter);
}

if (!filterRequested && listener.isSussessful()) {
Expand Down

This file was deleted.

This file was deleted.

0 comments on commit c0a67d7

Please sign in to comment.