我有以下内容:
- ExampleLogic.java(使用异步服务进行服务器调用的类(
- ExampleServiceAsync.java(Interfaceála GWT(
- ExampleService.java(用于创建异步实例的Interfaceála GWT(
- ExampleLogicTest(这是出现错误的地方(
我有两个简单的测试,当分别运行时,它们都通过了。但当它们相继运行时(在Eclipse中(,第二个测试总是失败,并出现以下错误:
需要但未调用:exService.exampleServiceMethod((。实际上,与该mock的交互为零。
我对服务进行了如下注释:@GwtMock exService;
需要注意的是,调用异步服务的ExampleLogic类在自己的类中创建服务。正如您在示例中看到的那样,我可以通过设置测试类中的异步服务来实现它。但我只需要Mockito的@Mock
。
它是有效的,因此这个问题更多的是出于好奇,只是一点实用性(因为感觉没有必要仅仅为了测试而为异步服务设置一个setter(。
所以问题是:
为什么会是这样
其他问题:
对此有什么可做的吗?你推荐另一种测试方式吗
希望有任何GWT专家可以帮助我!
使用:
JUnit 4.13
GwtLockito 1.1.9(以及下面的Mockito:0.9.2(
ExampleLogic.java(使用异步服务进行服务器调用的类(
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(
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(这是出现错误的地方(
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);
}
}
D_6的猜测是正确的-这是您的问题。由于这个Util.instance字段是静态的,并且在测试完成时没有任何东西会将其清空,因此对Util.getInstance((的下一次调用必须始终返回相同的值,因此永远不会创建mock。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? }
需要考虑的一些可能选项:
首先,以这种方式创建服务是非常便宜的,无论如何,所有的服务调用都可能被重新映射为静态方法,因此创建服务或保留实例几乎没有实际成本。这意味着您可以在每次调用该方法时,甚至每次调用任何服务方法时,创建一个新的服务实例。Singleton应该用于状态对共享很重要的地方,或者创建成本很高的地方——至少从这里共享的代码来看,这两种情况都不正确。
在此基础上,您还可以根据需要直接GWT.create(…(服务,而不是使用持有实例的Util类型。
或者,假设您确实希望控制对实例的访问(也许是为了允许在创建服务时对其进行自定义配置,等等(,而不是在类中使用静态字段来保存它,请考虑使用常规字段,以便可以在每个测试中实例化新的持有者。如果您不想要完全的DI工具,您仍然可以创建一个实例来提供这些对象。有一个只测试的方法,在tearDown((等过程中清空实例。
依赖注入("DI"(工具的GWT选项的快速摘要:
- 杜松子酒:在GWT2中;杜松子酒+桂酒";非常流行,但不再进行维护,并且将与J2CL(另一个可以处理大部分GWT2输入的编译器(不兼容,但非常灵活。Gin是Guice的一个子集,可以在GWT中工作
- Dagger2是另一种选择,但并不是专门为在GWT中工作而构建的。有很多例子展示了GWT2+Dagger2,这里有一个例子反映了他们的文档https://github.com/ibaca/gwt-dagger2-coffee
- Errai,在它的其他功能中,可以充当CDI容器,但也添加了许多其他功能——几乎可以肯定的是,它对你来说太过分了。对于今天开始的一个新项目,我不会考虑它
- crystnife是另一个选项,专门为在j2cl中工作而构建,但仍在进行中(尽管工件以v0.1的形式发布到maven central(。这被设计为只是Errai的CDI功能,并且更加轻量级