I have the following:
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);
}
}
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: