Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Failure triggered by MockRetrofit's NetworkBehavior even though error level set to 0% #2355

Closed
alixwar opened this issue Jun 2, 2017 · 7 comments
Labels

Comments

@alixwar
Copy link

alixwar commented Jun 2, 2017

I have a test that uses the Mock Retrofit classes that fails (not always).

My setup (somewhat relevant dependencies):
testCompile 'junit:junit:4.12'
testCompile 'org.mockito:mockito-all:1.10.19'
testCompile 'org.robolectric:robolectric:3.3.2'
testCompile 'org.mockito:mockito-all:1.10.19'
testCompile 'com.squareup.retrofit2:retrofit-mock:2.3.0'
compile ('com.squareup.retrofit2:retrofit:2.3.0') {
// exclude Retrofit’s OkHttp peer-dependency module and define your own module import
exclude module: 'okhttp'
}
compile 'com.squareup.okhttp3:okhttp-urlconnection:3.8.0'
compile 'com.squareup.okhttp3:okhttp:3.8.0'

Code:

// Note: connection is a Mockito spy

@Test
public void Login_WhenOkFromServer_DoesNotCrash() throws Exception {
    final NetworkBehavior behavior = NetworkBehavior.create();
    behavior.setDelay(0, TimeUnit.MICROSECONDS);
    behavior.setErrorPercent(0);
    mockRetrofit = new MockRetrofit.Builder(connection.createRetrofit(SERVER_URL))
            .networkBehavior(behavior)
            .build();
     final BehaviorDelegate<SessionRestInterface> sessionDelegate = mockRetrofit.create(SessionRestInterface.class);
     final List<SystemDto> expected = Collections.singletonList(new SystemDtoBuilder().build());
     final SessionRestInterface sessionRestInterface = new SuccessfulSessionRestInterface(sessionDelegate, expected);
     doReturn(sessionRestInterface).when(connection).getSessionRestInterface(SERVER_URL);
     Main.CallFromTestsOnly.setRetrofit(connection, SERVER_URL, mockRetrofit.retrofit());

     connection.login(CREDENTIALS);
}

When it fails, I get this stacktrace:

com.myapp.android.backend.BackendException: com.myapp.android.backend.BackendException@5176ff18[message=Caught in test.,endpoint=MyEndpoint,httpResponseCode=UNKNOWN]
com.myapp.android.backend.BackendException@5176ff18[message=Caught in test.,endpoint=MyEndpoint,httpResponseCode=UNKNOWN]
    at com.myapp.android.backend.administration.MainTest$1.answer(MainTest.java:86)
    at      com.myapp.android.backend.administration.MainTest.Login_WhenOkFromServer_DoesNotCrash(MainTest.java:280)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
    at      org.robolectric.RobolectricTestRunner$HelperTestRunner$1.evaluate(RobolectricTestRunner.java:488)
    at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
    at org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:27)
    at org.robolectric.internal.SandboxTestRunner$2.evaluate(SandboxTestRunner.java:209)
    at org.robolectric.internal.SandboxTestRunner.runChild(SandboxTestRunner.java:109)
    at org.robolectric.internal.SandboxTestRunner.runChild(SandboxTestRunner.java:36)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
    at org.robolectric.internal.SandboxTestRunner$1.evaluate(SandboxTestRunner.java:63)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
    at      org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecuter.runTestClass(JUnitTestClassExecuter.java:114)
    at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecuter.execute(JUnitTestClassExecuter.java:57)
    at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassProcessor.processTestClass(JUnitTestClassProcessor.java:66)
    at org.gradle.api.internal.tasks.testing.SuiteTestClassProcessor.processTestClass(SuiteTestClassProcessor.java:51)
    at sun.reflect.GeneratedMethodAccessor14.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:35)
    at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24)
    at org.gradle.internal.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:32)
    at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:93)
    at com.sun.proxy.$Proxy3.processTestClass(Unknown Source)
    at org.gradle.api.internal.tasks.testing.worker.TestWorker.processTestClass(TestWorker.java:109)
    at sun.reflect.GeneratedMethodAccessor13.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:35)
    at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24)
    at org.gradle.internal.remote.internal.hub.MessageHub$Handler.run(MessageHub.java:377)
    at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:54)
    at org.gradle.internal.concurrent.StoppableExecutorImpl$1.run(StoppableExecutorImpl.java:40)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
    at java.lang.Thread.run(Thread.java:745)
Caused by: com.myapp.android.backend.administration.MyException@731bd1e3[message=retrofit2.mock.MockRetrofitIOException: Failure triggered by MockRetrofit's NetworkBehavior,httpResponseCode=UNKNOWN]
    at com.myapp.android.backend.administration.MyConnection$3.executeWithUrls(MyConnection.java:157)
    at com.myapp.android.backend.administration.MyConnection$3.executeWithUrls(MyConnection.java:140)
    at com.myapp.android.backend.administration.MainTest$1.answer(MainTest.java:84)
    at org.mockito.internal.stubbing.StubbedInvocationMatcher.answer(StubbedInvocationMatcher.java:34)
    at org.mockito.internal.handler.MockHandlerImpl.handle(MockHandlerImpl.java:91)
    at org.mockito.internal.handler.NullResultGuardian.handle(NullResultGuardian.java:29)
    at org.mockito.internal.handler.InvocationNotifierHandler.handle(InvocationNotifierHandler.java:38)
    at org.mockito.internal.creation.cglib.MethodInterceptorFilter.intercept(MethodInterceptorFilter.java:59)
    at com.myapp.android.backend.urllookup.UrlLookupService$$EnhancerByMockitoWithCGLIB$$795f6cb0.retrieveUrlsAndExecute(<generated>)
    at com.myapp.android.backend.administration.MyConnection.login(MyConnection.java:139)
    at com.myapp.android.backend.administration.MyConnection$$EnhancerByMockitoWithCGLIB$$607f8516.CGLIB$login$16(<generated>)
    at com.myapp.android.backend.administration.MyConnection$$EnhancerByMockitoWithCGLIB$$607f8516$$FastClassByMockitoWithCGLIB$$c3a1e45e.invoke(<generated>)
    at org.mockito.cglib.proxy.MethodProxy.invokeSuper(MethodProxy.java:216)
    at org.mockito.internal.creation.cglib.DelegatingMockitoMethodProxy.invokeSuper(DelegatingMockitoMethodProxy.java:19)
    at org.mockito.internal.invocation.realmethod.DefaultRealMethod.invoke(DefaultRealMethod.java:21)
    at org.mockito.internal.invocation.realmethod.CleanTraceRealMethod.invoke(CleanTraceRealMethod.java:30)
    at org.mockito.internal.invocation.InvocationImpl.callRealMethod(InvocationImpl.java:112)
    at org.mockito.internal.stubbing.answers.CallsRealMethods.answer(CallsRealMethods.java:41)
    at org.mockito.internal.handler.MockHandlerImpl.handle(MockHandlerImpl.java:93)
    at org.mockito.internal.handler.NullResultGuardian.handle(NullResultGuardian.java:29)
    at org.mockito.internal.handler.InvocationNotifierHandler.handle(InvocationNotifierHandler.java:38)
    at org.mockito.internal.creation.cglib.MethodInterceptorFilter.intercept(MethodInterceptorFilter.java:59)
    at com.myapp.android.backend.administration.MyConnection$$EnhancerByMockitoWithCGLIB$$607f8516.login(<generated>)
    ... 46 more
Caused by: retrofit2.mock.MockRetrofitIOException: Failure triggered by MockRetrofit's NetworkBehavior
@JakeWharton
Copy link
Collaborator

Weird. Will look.

@JakeWharton JakeWharton added the Bug label Jun 2, 2017
@JakeWharton
Copy link
Collaborator

Ok. So the problem is that you're calling setErrorPercent(0) and you should be calling setFailurePercent(0).

The difference is subtle. Failures are IOExeptions that happen during the request. Errors are successful responses that do not have a 200 status code. By default errors already happen 0% of the time so your call basically does nothing. Failures, however, happen 3% of the time, and this is the value that you need to set to 0.

@alixwar
Copy link
Author

alixwar commented Jun 5, 2017

Thanks for the clarifications! Whether the default behavior is good or not is a different discussion I guess...

@JakeWharton
Copy link
Collaborator

JakeWharton commented Jun 5, 2017 via email

@alixwar
Copy link
Author

alixwar commented Jun 5, 2017

Because of the final classes in Retrofit, there is a need for built-in fakes unless going for bytecode manipulation frameworks such as PowerMock (rather not). For this reason we use MockRetrofit in our unit tests.

@JakeWharton
Copy link
Collaborator

JakeWharton commented Jun 5, 2017 via email

@alixwar
Copy link
Author

alixwar commented Jun 5, 2017

It's all about abstraction. I would like to keep Retrofits, Calls and Responses (and the API services) within one abstraction layer and to test that layer I have the choice to either mock the retrofits completely or to use fakes, such as MockRetrofit, which I think reduces some coupling at least. Since most classes in Retrofit are final I have embraced this approach.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants