Reputation: 1359
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?
Upvotes: 4
Views: 1985
Reputation: 16082
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())
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.
Upvotes: 3