5

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.

Donal Rafferty
  • 19,707
  • 39
  • 114
  • 191
  • 1
    http://stackoverflow.com/questions/36969659/robolectric-3-with-fabric-crashlytics/36980405#36980405 – Eugen Martynov Jun 08 '16 at 05:55
  • @EugenMartynov - I have tried that approach, following this - http://robolectric.org/custom-test-runner/ - and I still get the same issue, its like its ignoring the TestApplication class – Donal Rafferty Jun 08 '16 at 07:27
  • 1
    if you tried that approach then what did you changed? It should create app for every test - unit tests should be independent and repeatable – Eugen Martynov Jun 08 '16 at 08:08
  • 1
    http://stackoverflow.com/questions/34695552/robolectric-with-activeandroid-setup-nullpointerexception-on-activeandroidrefle/34695868#34695868 – Eugen Martynov Jun 08 '16 at 08:10
  • @EugenMartynov - I have updated my question with the changes I have made – Donal Rafferty Jun 08 '16 at 08:33
  • 1
    If you remove init the Picasso for tests at all. What I usually do I abstract Picasso or at least it static nature and mock it in tests if necessary (usually it is since my coverage is always close to 90%) – Eugen Martynov Jun 08 '16 at 08:34
  • @EugenMartynov - from the TestMyApplication class? I get the same issue when I do this, its very much like Robolectric is completely ignoring or can't see the TestMyApplication.java file. – Donal Rafferty Jun 08 '16 at 08:38
  • 1
    Ah sorry, can you override it in your application test and leave it empty, so no Picasso is instantiated – Eugen Martynov Jun 08 '16 at 08:38
  • @EugenMartynov - Yes that is what I am attempting to do, I don't need the Picasso at all but Robolectric seems to load MyApplication.java each time a test is run which is not what I want. I have overwritten the initPicasso method in TestMyApplication.java but Robolectric still runs the initPicasso method in MyApplication.java on each test, Note that the initPicasso is called on the onCreate method of MyApplication.java, this may be the issue? – Donal Rafferty Jun 08 '16 at 08:42
  • It should. First, remove mentioning of application in config - it is not needed. Second check package names and folders. Do you have special applications for flavours, do you have flavour that change app id? – Eugen Martynov Jun 08 '16 at 08:46
  • 1
    If you put a breakpoint in real app `initPicasso` does it stop in tests? Do you have another place which initiates Picasso? – Eugen Martynov Jun 08 '16 at 08:49
  • @EugenMartynov - no there is only one place where it gets called, in the onCreate of the MyApplication.java class. When I put a breakpoint in there it doesn't stop there when I run the test. However the error returned is from MyApplication.java class - "initPicasso(MyApplication.java:329)" – Donal Rafferty Jun 08 '16 at 09:03
  • @EugenMartynov - Please see my answer for how I got it to work, if you have time I would like clarification from an expert that this pattern is OK to use. – Donal Rafferty Jun 08 '16 at 09:44

2 Answers2

5

In Robolectric 3.4.2, use a custom Application class in tests works:

public class MyApplication extends Application {

    protected void initPicasso() {
        // do Picasso initialization
    }

}

public class TestMyApplication extends MyApplication {

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

}

Just add a Config annotation in your test classes:

@Config(constants = BuildConfig.class, application = TestMyApplication.class)
glucas
  • 259
  • 2
  • 8
4

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.

Donal Rafferty
  • 19,707
  • 39
  • 114
  • 191