Mockito: verifying method call from internal anonymous class
Asked Answered
C

1

2

I have a class under test which contains a method which has an inner anonymous class. One of the methods in the anonymous class calls a method from the class under test, but Mockito doesn't seem to realize this.

public class ClassUnderTest {
    Dependency dependency;
    public ClassUnderTest(Dependency d) {
        dependency = d;
    }
    public void method() {
        dependency.returnsObservable().observeOn(AndroidSchedulers.mainThread())
            .subscribeOn(Schedulers.io()).subscribe(new Observer<SupportClass> {

            /* Other methods omitted */
            public void onComplete() {
                 outerMethod();
            })
    }

    public void outerMethod() {
        blah;
    }
}

My test code:

public class TestClass {

    ClassUnderTest underTest;
    Dependency dependency;

    @Before
    public void setUp() throws Exception {

        dependency = Mockito.mock(Dependency.class);
        underTest = Mockito.spy(new ClassUnderTest(dependency));

    }

    @Test
    public void method() throws 
        Mockito.when(dependency.returnObservable()).thenReturn(Observable.just(new SupportClass());

        Mockito.doNothing().when(underTest).outerMethod();

        underTest.method();
        Mockito.verify(underTest).outerMethod();

    }

}

For some reason that I can't seem to figure out, Mockito can't detect that outerMethod() is being called, even though I have manually verified by stepping through line by line in the debugger. I have also verified that the call to the dependency object returns the proper observable with the correct content, and the onComplete() and outerMethod() methods do get called. I'm just confused why Mockito doesn't detect it as such.

This is the error that it spits out:

Wanted but not invoked:
classUnderTest.outerMethod();
-> at (file and line number)

However, there was exactly 1 interaction with this mock:
classUnderTest.method();
-> at (file and line number)

Is there anything obvious I'm missing?

Coates answered 29/12, 2017 at 2:50 Comment(6)
Show he Dependency class. I'm not seeing where innerMethod is being called. Would be best to post a MCVEDoyledoyley
I've update the example code to hopefully clear some things up. The returnObservable() method in the dependency class is working correctly/returning a valid Observable object, and the onComplete() and outerMethod() methods do get called, I'm just confused as to why Mockito doesn't detect it. @VinceEmighCoates
Your code is not sufficient. In your test dependency is a mock, but you call dependency.returnsObservable().subscribe(...). That would cause NPE in your test because dependency.returnsObservable() would return null. Make your example work.Backstage
I can't see where the returned Observables onComplete method is called.Americana
Sorry @Backstage forgot to include the method mock, it should work now.Coates
@Americana I'm actually not 100% sure how Observables work yet, but onComplete() does get called at some point (I think there's a lot of behind the scenes stuff going on with theObservable object that causes it to get called, it's almost like an onClick() method, it's triggered by something and not necessarily called explicitly).Coates
M
3

You're changing between schedulers so it can cause some issues when testing (your code may reach the verify method before the actual method is invoked

Check this article explaining how to test asynchronous code with RxJava and Mockito

TL;DR

Add a TestRule that set all schedulers to trampoline so it behaves synchronously:

public class TrampolineSchedulerRule implements TestRule {
  @Override
  public Statement apply(final Statement base, Description d) {
    return new Statement() {
      @Override
      public void evaluate() throws Throwable {
        RxJavaPlugins.setIoSchedulerHandler(
            scheduler -> Schedulers.trampoline());
        RxJavaPlugins.setComputationSchedulerHandler(
            scheduler -> Schedulers.trampoline());
        RxJavaPlugins.setNewThreadSchedulerHandler(
            scheduler -> Schedulers.trampoline());
        RxAndroidPlugins.setInitMainThreadSchedulerHandler(
            scheduler -> Schedulers.trampoline());

        try {
          base.evaluate();
        } finally {
          RxJavaPlugins.reset();
          RxAndroidPlugins.reset();
        }
      }
    };
  }
}
Mite answered 29/12, 2017 at 8:26 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.