pivotal/robolectric — Issue #156: RobolectricTestRunner's use of ClassLoader breaks Mockito when package private mocks are shared
Author: richardszalay
Created: 2011-11-09 15:34:18+00:00
State: open

RobolectricTestRunner has several issues with its use of a custom class loader:

- It changes the class loader in it's constructor (which can be run long before the class's tests are run - other tests can run in between)
- It never resets the class loader

This behavior manifests itself when two test classes (one Roboelctric, one Mockito) both mock the same package-private class/interface. When this occurs, the Mockito class will fail.

Below is a workaround RoboelectricTestRunner that resolves the issue by:

- Capturing the default/roboelectric class loader in the constructor
- Resetting the default class loader after the RoboelectricTestRunner constructor has run
- Setting the roboelectric class loader before the tests for that class are run
- Resetting the default class loader after the tests for that class are run

```java
public class RobolectricTestRunnerClassLoaderFix extends BlockJUnit4ClassRunner {
private final BlockJUnit4ClassRunner delegate;

private final ClassLoader defaultClassLoader;
private final ClassLoader roboElectricClassLoader;

public RobolectricTestRunnerClassLoaderFix(final Class testClass) throws InitializationError {
super(testClass);

// "Default" class loader
defaultClassLoader = Thread.currentThread().getContextClassLoader();

delegate = new RobolectricTestRunner(testClass);

// "Roboelectric" class loader
roboElectricClassLoader = Thread.currentThread().getContextClassLoader();

// Loaders are constructed before _any_ tests are run, which may not start with roboelectric ones
resetClassLoader();
}

private void resetClassLoader() {
Thread.currentThread().setContextClassLoader(defaultClassLoader);
}

@Override
public boolean equals(Object obj) {
return delegate.equals(obj);
}

@Override
public Description getDescription() {
return delegate.getDescription();
}

@Override
public void filter(Filter filter) throws NoTestsRemainException {
delegate.filter(filter);
}

@Override
public int hashCode() {
return delegate.hashCode();
}

@Override
public int testCount() {
return delegate.testCount();
}

@Override
public void run(RunNotifier notifier) {

try {
Thread.currentThread().setContextClassLoader(roboElectricClassLoader);

delegate.run(notifier);
} finally {
resetClassLoader();
}
}

@Override
public void sort(Sorter sorter) {
delegate.sort(sorter);
}

@Override
public void setScheduler(RunnerScheduler scheduler) {
delegate.setScheduler(scheduler);
}

@Override
public String toString() {
return delegate.toString();
}

}
```