Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Robolectric - Application has Singletons in setup, causing issues with tests

I have currently inherited an Android application that has zero code coverage in it and my first job is to get some unit tests written for it. So I have decided to also use it as an opportunity to learn Robolectric.

However I am hitting initial issues getting two simple dummy tests to run.

Here is my code in my test file:

@Config(constants = BuildConfig.class)
@RunWith(RobolectricGradleTestRunner.class)
public class SplashActivityTest {

private SplashActivity activity;

// @Before => JUnit 4 annotation that specifies this method should run before each test is run
// Useful to do setup for objects that are needed in the test
@Before
public void setup() {
    // Convenience method to run SplashActivity through the Activity Lifecycle methods:
    // onCreate(...) => onStart() => onPostCreate(...) => onResume()
    activity = Robolectric.setupActivity(SplashActivity.class);
}

// @Test => JUnit 4 annotation specifying this is a test to be run
// Checking that the UI gets setup correctly
@Test
public void dummy() {
  String test = "POP!";

    assertTrue("POP!",
            test.equals("POP!"));
}

@Test
public void dummyTwo() {

    String test = "POP!!";

    assertTrue("POP!!",
            test.equals("POP!!"));
}

}

The problem is the activity extends another class called baseactivity and in this class a custom Application class is used.

In this custom application class Picasso is created as a Singleton using the following code:

picasso = new Picasso.Builder(getApplicationContext()).downloader(new OkHttpDownloader(picassoClient)).build();
    Picasso.setSingletonInstance(picasso);

When I run the tests I get the following error:

java.lang.IllegalStateException: Singleton instance already exists. at com.squareup.picasso.Picasso.setSingletonInstance(Picasso.java:677)

So it looks like the application class is getting created twice, once for each test as with one test it runs fine. So I am assuming my pattern for testing is wrong here? Can anyone help me out with a correct pattern? As with an Unit test I want to just test limited functionality so I am not sure what I am doing is correct.

EDIT: I have tried to setup a "mock" application class and get Robolectric to use it but it still seems to use the real Application class.

So in test/java I have the following class:

public class TestMyApplication extends MyApplication
        implements TestLifecycleApplication {

    @Override
    public void onCreate() {
        super.onCreate();
        initPicasso();
    }

    @Override
    protected void initPicasso() {
        //nothing to do
    }

    @Override public void beforeTest(Method method) {
    }

    @Override public void prepareTest(Object test) {
    }

    @Override public void afterTest(Method method) {
    }
}

As you can see it extends the MyApplication class which is in my main app and I have also added @Override to the initPicasso method to try to stop it getting called, however when I run my tests I still get the error where the Picasso Singleton is set a second time for the second test.

So when I run my test class it still goes into the Application class in my main app, why does Robolectric do this when Unit tests should be limited in scope?

I have also tried this:

@Config(constants = BuildConfig.class, application = TestMyApplication.class)

But when I try this and run the test class I get an error saying it can't find TestMyApplication, so its a tearing my hair out issue as to why Robolectric wont use my mocked Application class.

like image 553
Donal Rafferty Avatar asked Jun 07 '16 15:06

Donal Rafferty


1 Answers

To get it to work I had to create the following class which extends the RobolectricGradleTestRunner and force it to use the TestMyApplication class.

public class TestRunner extends RobolectricGradleTestRunner {

    public TestRunner(final Class<?> testClass) throws InitializationError {
        super(testClass);
    }

    @Override
    protected Class<? extends TestLifecycle> getTestLifecycleClass() {
        return MyTestLifecycle.class;
    }

    public static class MyTestLifecycle extends DefaultTestLifecycle {
        @Override
        public Application createApplication(final Method method, final AndroidManifest appManifest, final Config appConfig) {
            // run tests under our TestApplication
            return new TestMyApplication();
        }
    }

}

Then in the TestMyApplication class I had to override the initPicasso method:

@Override protected void initPicasso(){
        //do nothing
    }

Only after doing this did Robolectric finally skip the initPicasso in the main MyApplication.java class.

like image 158
Donal Rafferty Avatar answered Nov 15 '22 03:11

Donal Rafferty