Search code examples
javajunitgwtrpcgwtmockito

GwtMockito: Single tests pass and multiple fails when using Async-service within tested class. (GwtMock- and GWT.create-related)


I have the following:

  • ExampleLogic.java (Class that uses an Async-service to do a server call)
  • ExampleServiceAsync.java (Interface á la GWT)
  • ExampleService.java (Interface á la GWT to create the async-instance)
  • ExampleLogicTest (This is where the error is seen)

I have two simple tests and when run separately they both pass. But when running them after each other (in Eclipse) the second one always fails with the following error:

Wanted but not invoked: exService.exampleServiceMethod(). Actually there were zero interactions with this mock.

I've annotated the service like this: @GwtMock exService;
Important to note is that the ExampleLogic-class which calls the async-service creates the service in its own class. And I can make it work, as you can see in the examples, by setting the async-service from the test-class. But then I only need the @Mock from Mockito instead.

It works and therefore this question is more out of curiousity and just a little bit of utility (because it feels unnecessary to have a setter for the async-service only for the sake of testing).

So the question is:
Why is it like this?
Other questions:
Is there anything to do about it? Do you recommend another way of testing?

Hope there are any GWT-experts out there that can help me!

Using:
JUnit 4.13
GwtMockito 1.1.9 (And the Mockito that follows: 0.9.2)

ExampleLogic.java (Class that uses an Async-service to do a server call)

import com.google.gwt.user.client.rpc.AsyncCallback;

public class ExampleLogic {
  public boolean callFailed; // public to simplify example
  public boolean returnVal; // public to simplify example
  private ExampleServiceAsync exampleService;
  
  public void setExampleService(ExampleServiceAsync exampleService) {
    this.exampleService = exampleService;
  }

  public void exampleCallToService() {
    if (exampleService == null) {
      exampleService = ExampleService.Util.getInstance(); // Problem arises here.
      // I suppose GwtMockito is reusing the old one even though GwtMockito.tearDown() is called.
      // That's why the second fails with the comment "There were zero interactions with this mock".
      // It is actually using the first still. Why is that so and how can I make it use the second?
    }
    
    exampleService.exampleServiceMethod(new AsyncCallback<Boolean>() {
      
      @Override
      public void onSuccess(Boolean result) {
        callFailed = false;
        returnVal = result;
      }
      
      @Override
      public void onFailure(Throwable caught) {
        callFailed = true;
      }
    });
  }
}

ExampleServiceAsync.java (Interface á la GWT)

import com.google.gwt.http.client.Request;
import com.google.gwt.user.client.rpc.AsyncCallback;

public interface ExampleServiceAsync {
  public Request exampleServiceMethod(AsyncCallback<Boolean> callback);
}

ExampleService.java (Interface á la GWT to create the async-instance)

import com.google.gwt.core.client.GWT;
import com.google.gwt.user.client.rpc.RemoteService;

public interface ExampleService extends RemoteService {
  public static class Util {
    private static ExampleServiceAsync instance = null;
    public static ExampleServiceAsync getInstance(){
      if (instance == null) {
        instance = (ExampleServiceAsync) GWT.create(ExampleService.class);
      }
      
      return instance;
    }
  }
  
  boolean exampleServiceMethod();
}

ExampleLogicTest (This is where the error is seen)

import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.Mockito;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwtmockito.GwtMock;
import com.google.gwtmockito.GwtMockito;
import com.google.gwtmockito.GwtMockitoTestRunner;

@RunWith(GwtMockitoTestRunner.class)
public class ExampleLogicTest {

  @GwtMock ExampleServiceAsync exService;
  // @Mock ExampleServiceAsync exService; // Can be used if the service is set manually
  @Captor ArgumentCaptor<AsyncCallback<Boolean>> callbackCaptor;
  ExampleLogic exLogic;

  @Before
  public void init() {
    GwtMockito.initMocks(this); // Doesn't make any difference to comment/uncomment.
    exLogic = new ExampleLogic();
//    exLogic.setExampleService(exService); // Uncommenting this will make both tests pass in a single run. Otherwise the second to run will always fail. Or running separately they'll pass.
  }

  @After
  public void tearDown() {
    GwtMockito.tearDown(); // Doesn't make any difference to comment/uncomment.
  }

  @Test
  public void test1_SuccessfulCall() {
    exLogic.exampleCallToService();
    Mockito.verify(exService).exampleServiceMethod(callbackCaptor.capture());
    AsyncCallback<Boolean> callback = callbackCaptor.getValue();
    callback.onSuccess(true);
    assertFalse(exLogic.callFailed);
    assertTrue(exLogic.returnVal);
  }

  @Test
  public void test2_FailedCall() {
    exLogic.exampleCallToService();
    Mockito.verify(exService).exampleServiceMethod(callbackCaptor.capture());
    AsyncCallback<Boolean> callback = callbackCaptor.getValue();
    callback.onFailure(new Throwable());
    assertTrue(exLogic.callFailed);
    assertFalse(exLogic.returnVal);
  }

}

Solution

  • if (exampleService == null) {
       exampleService = ExampleService.Util.getInstance(); // Problem arises here.
       // I suppose GwtMockito is reusing the old one even though GwtMockito.tearDown() is called.
       // That's why the second fails with the comment "There were zero interactions with this mock".
       // It is actually using the first still. Why is that so and how can I make it use the second?
     } 
    
    public static class Util {
      private static ExampleServiceAsync instance = null;
      public static ExampleServiceAsync getInstance(){
        if (instance == null) {
          instance = (ExampleServiceAsync) GWT.create(ExampleService.class);
        }
     
        return instance;
      }
    }
    

    Your guess is correct - this is your problem. Since this Util.instance field is static, and nothing ever nulls it out when the test is done, the next call to Util.getInstance() must always return the same value, so the mock is never created.

    Some possible options to consider:

    First, creation of a service in this way is very cheap, it is possible that all of the service calls will be remade into static methods anyway, so there is nearly no actual cost to creating the service or keeping the instance around. This means you could go so far as to create a new service instance every time that the method is called, perhaps every time that any service method is called. Singletons should be used where state is important to be shared, or where the cost of creation is high - at least from the code shared here, neither of those is true.

    Extending from that, you could also just directly GWT.create(...) the services as you need them, instead of a Util type that holds the instance.

    Alternatively, and assuming you do want to control access to the instance (perhaps to allow for custom configuration of the service as it is created, etc), instead of a static field in a class to hold this, consider a regular field, so that a new holder can be instantiated as part of each test. If you don't want full-on DI tooling of some kind, you can still make a single instance to provide these objects. Have a test-only method that nulls out the instance during tearDown(), etc.


    Quick summary of GWT options for dependency injection ("DI") tools:

    • Gin: In GWT2, "gin+guice" is quite popular, but isn't maintained any more, and will not be compatible with J2CL (another compiler which can handle much of GWT2 inputs), but is very flexible. Gin is a subset of Guice's features that can work in GWT.
    • Dagger2 is another option, but not really purpose-built to work in GWT. There are many examples that demonstrate GWT2+Dagger2, here's one that mirrors their docs https://github.com/ibaca/gwt-dagger2-coffee
    • Errai, among its other features, can act as a CDI container, but also adds many other features - almost certainly overkill for you. I would not consider it for a new project started today.
    • Crysknife is another option, specifically built to work in j2cl, but is still in progress (though artifacts are published to maven central as v0.1). This was designed as if it were only Errai's CDI features, and is much more lightweight.