4

I have a fragment that I am trying to test using Robolectric (and Mockito) which uses a @Singleton api class. I am trying to mock the singleton in a way where I can customize the response for each test. Here is my API class that my fragment references:

@Singleton
public class MyApi {
    @Inject
    public MyApi(Context context) {
        //Do something
    }

    public MyObject getMyFeed(){
    }
}

Here is my test class that I'm trying to set up:

@RunWith(RobolectricTestRunner.class)
public class MyFragmentTest extends TestCase {

    @Inject MyApiInterceptor myApi;
    @Inject Activity shadowActivity;
    @Inject LayoutInflater shadowLayoutInflator;
    @Inject ViewGroup shadowViewGroup;

    @Before
    public void setUp() throws Exception {
        Robolectric.bindShadowClass(SingleThreadActivity.class);
        ShadowApiModule module = new ShadowApiModule();
        Module roboGuiceModule = RoboGuice.newDefaultRoboModule(Robolectric.application);
        RoboGuice.setBaseApplicationInjector(Robolectric.application, RoboGuice.DEFAULT_STAGE,
                roboGuiceModule);
        RoboInjector injector = RoboGuice.getInjector(Robolectric.application);
        injector.injectMembers(this);
    }
    @After
    public void tearDown() {
        RoboGuice.util.reset();
        Application app = Robolectric.application;
        DefaultRoboModule defaultModule = RoboGuice.newDefaultRoboModule(app);
        RoboGuice.setBaseApplicationInjector(app, RoboGuice.DEFAULT_STAGE, defaultModule);
    }

    @Test
    public void testOnCreateView() throws Exception{

        myApi.mock = mock(MyApi.class);
        when(myApi.mock.getMyFeed()).thenReturn(new MyObject());

        MyFragment frag = new MyFragment();
        frag.onAttach(shadowActivity);
        frag.onCreate(null);
        frag.onCreateView(shadowLayoutInflator,shadowViewGroup, null);
        frag.onActivityCreated(null);
        frag.onStart();
        frag.onResume();
    }
}

@Singleton
class MyApiInterceptor extends MyApi
{
    public MyApi mock;

    @Inject
    public MyApiInterceptor(Context context) {
        super(context);
    }

    @Override
    public MyObject getMyFeed() throws Exception {
        return mock.getMyFeed();
    }
}
@Implements(Activity.class)
class SingleThreadActivity extends ShadowActivity{

    @Override
    public void runOnUiThread(Runnable action) {
        action.run();
    }
}

class ShadowApiModule extends AbstractModule {

    @Override
    protected void configure() {
        bind(Context.class).to(MockContext.class);
        bind(ViewGroup.class).toInstance(mock(ViewGroup.class));
        bind(MyApi.class).to(MyApiInterceptor.class);
    }
}

However when I run the test I get the following:

com.google.inject.ConfigurationException: Guice configuration errors:

1) No implementation for android.view.ViewGroup was bound.
  while locating android.view.ViewGroup
    for field at com.mysource.MyFragmentTest.shadowViewGroup(Unknown Source)
  while locating com.mysource.MyFragmentTest

1 error
    at com.google.inject.internal.InjectorImpl.getMembersInjector(InjectorImpl.java:952)
    at com.google.inject.internal.InjectorImpl.getMembersInjector(InjectorImpl.java:957)
    at com.google.inject.internal.InjectorImpl.injectMembers(InjectorImpl.java:943)
    at roboguice.inject.ContextScopedRoboInjector.injectMembersWithoutViews(ContextScopedRoboInjector.java:243)
    at roboguice.inject.ContextScopedRoboInjector.injectMembers(ContextScopedRoboInjector.java:236)
    at com.mysource.MyFragmentTest.setUp(RSSFeedActivityTest.java:58)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:44)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:41)
    at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:27)
    at org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:31)
    at com.xtremelabs.robolectric.RobolectricTestRunner$1.evaluate(RobolectricTestRunner.java:292)
    at org.junit.runners.BlockJUnit4ClassRunner.runNotIgnored(BlockJUnit4ClassRunner.java:79)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:71)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:49)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:193)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:52)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:191)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:42)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:184)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:236)
    at org.junit.runner.JUnitCore.run(JUnitCore.java:157)
    at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:76)
    at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:195)
    at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:63)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:120)

From what I understand about roboguice, it seems that it can't inject the "shadowViewGroup" with a ShadowViewGroup for some reason but I'm not sure why. If i'm going about this the wrong way then please let me know that but this seems like it should work.

Can you all tell me either: 1)Why my test isn't working or 2)A better way to inject a custom singleton that a class uses?

BoredAndroidDeveloper
  • 1,251
  • 1
  • 11
  • 26

1 Answers1

3

ViewGroup implementation is not visible. You have it declared in ShadowApiModule which is not passed to RoboGuice.

Instead:

Module roboGuiceModule = RoboGuice.newDefaultRoboModule(Robolectric.application);

try this:

Module roboGuiceModule = Modules.override(RoboGuice.newDefaultRoboModule(Robolectric.application)).with(new ShadowApiModule())

Answers to the comment:


If you have problems with singleton in test then just remove it. Tests should be isolated, so singleton pattern is not necessary there.

In production code, instead of @Singleton class annotation, set binding with Singleton.class in Module

bind(MyApi.class).to(MyApiImpl.class).in(Singleton.class);

And for test module, in your case ShadowModule, set binding without Singleton.

bind(MyApi.class).to(MyApiImpl.class);

The other case with injecting shadow classes. You should inject objects which have to get injected members or have different implementation. There is no need to inject ViewGroup or Activity unless there're bindings for custom implementations.

Look at the first Robolectric example: http://pivotal.github.com/robolectric/
There is only constructor and it works.

pawelzieba
  • 16,082
  • 3
  • 46
  • 72
  • That appears to get past the error but then I get additional errors due to other shadow injections not working. It seems like somehow I have prevented the normal shadow objects from being created. Do you know how I can re-enable those? Further evidence for this is that when I remove the two lines in ShadowApiModule to mock the context and viewgroup then I get the whole "1) No implementation for android.view.ViewGroup was bound." error again. – BoredAndroidDeveloper Nov 06 '12 at 21:00
  • I don't have enough information to help you with new problems. I don't know why you're injecting `Activity` or `ViewGroup`. I think you ought to study how Robolectric and Guice work. – pawelzieba Nov 06 '12 at 22:18
  • I'm injecting Activity and ViewGroup because I need shadow instances of them to pass into the Fragment's onAttach and onCreateView. This worked previously before I tried to override the @Singleton version of MyApi so I know that I broke that. I don't disagree that I need to look into it more though. If you're not sure of why I'm approaching the problem this way, do you know of a better way to create this type of test? – BoredAndroidDeveloper Nov 06 '12 at 22:44
  • You've written about other not working injections - that's not enough informations for me to help you. I've edited my answer. – pawelzieba Nov 07 '12 at 10:12
  • Excellent updated answer and I'll give the credit to you. It turns out that since I'm using RoboSherlock there are other issues where the activity that needs to be passed in needs to extend RoboSherlockActivity. As well I've updated my tests to use a Mock MyApi and then I put this statement into my test module: bind(MyApi.class).toInstance(myMockApiInstance); I still don't have it working but I'm updating my tests and code and things are looking more positive. – BoredAndroidDeveloper Nov 07 '12 at 15:38