GwtMockito:在测试 class 中使用异步服务时,单个测试通过,多个失败。 (GwtMock 和 GWT.create 相关)
GwtMockito: Single tests pass and multiple fails when using Async-service within tested class. (GwtMock- and GWT.create-related)
我有以下内容:
- ExampleLogic.java(Class 使用异步服务进行服务器调用)
- ExampleServiceAsync.java(GWT 接口)
- ExampleService.java(用于创建异步实例的 GWT 接口)
- ExampleLogicTest(这是出现错误的地方)
我有两个简单的测试,当分别 运行 时,它们都通过了。但是当 运行 将它们彼此连接时(在 Eclipse 中),第二个总是失败并出现以下错误:
Wanted but not invoked: exService.exampleServiceMethod(). Actually there were zero interactions with this mock.
我已经这样注释了服务:@GwtMock exService;
需要注意的重要一点是,调用异步服务的 ExampleLogic-class 在其自己的 class 中创建服务。正如您在示例中看到的那样,我可以通过从 test-class 设置异步服务来使其工作。但是后来我只需要来自 Mockito 的 @Mock
。
它有效,因此这个问题更多是出于好奇和一点实用性(因为感觉没有必要为异步服务设置 setter 只是为了测试)。
所以问题是:
为什么会这样?
其他问题:
有什么办法吗?你推荐另一种测试方法吗?
希望有任何 GWT 专家可以帮助我!
使用:
JUnit 4.13
GwtMockito 1.1.9(以及后面的 Mockito:0.9.2)
ExampleLogic.java(Class 使用异步服务进行服务器调用)
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(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(用于创建异步实例的 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);
}
}
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;
}
}
您的猜测是正确的 - 这是您的问题。由于这个 Util.instance 字段是静态的,并且在测试完成后没有任何东西将其清空,因此下一次调用 Util.getInstance() 必须始终 return 相同的值,因此模拟永远不会已创建。
一些可能的考虑选项:
首先,以这种方式创建服务的成本非常低,有可能所有的服务调用都将被重新制作成静态方法,因此创建服务或保留实例几乎没有实际成本大约。这意味着您甚至可以在每次调用该方法时创建一个新的服务实例,也许每次调用 any 服务方法时。单例应该用于状态共享很重要的地方,或者创建成本很高的地方 - 至少从这里共享的代码来看,这些都不是真的。
由此扩展,您还可以根据需要直接 GWT.create(...) 服务,而不是保存实例的 Util 类型。
或者,假设您确实想要控制对实例的访问(可能允许在创建服务时自定义配置等),而不是 class 中的静态字段来保存为此,请考虑一个常规字段,以便可以将新的持有者实例化为每个测试的一部分。如果您不想要某种完整的 DI 工具,您仍然可以创建一个实例来提供这些对象。有一个只测试方法,在 tearDown() 等期间清空实例
依赖注入(“DI”)工具的 GWT 选项快速总结:
- Gin: In GWT2, "gin+guice" 非常流行,但不再维护,并且不会与 J2CL(另一个可以处理大部分 GWT2 输入的编译器)兼容,但非常灵活。Gin 是一个可以在 GWT 中工作的 Guice 功能的子集。
- 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 除了它的其他功能外,还可以充当 CDI 容器,而且还添加了许多其他功能 - 几乎肯定对您来说太过分了。对于今天开始的新项目,我不会考虑它。
- Crysknife 是另一种选择,专门为在 j2cl 中工作而构建,但仍在进行中(尽管工件作为 v0.1 发布到 Maven Central)。这设计得好像它只是 Errai 的 CDI 功能,而且更轻量级。
我有以下内容:
- ExampleLogic.java(Class 使用异步服务进行服务器调用)
- ExampleServiceAsync.java(GWT 接口)
- ExampleService.java(用于创建异步实例的 GWT 接口)
- ExampleLogicTest(这是出现错误的地方)
我有两个简单的测试,当分别 运行 时,它们都通过了。但是当 运行 将它们彼此连接时(在 Eclipse 中),第二个总是失败并出现以下错误:
Wanted but not invoked: exService.exampleServiceMethod(). Actually there were zero interactions with this mock.
我已经这样注释了服务:@GwtMock exService;
需要注意的重要一点是,调用异步服务的 ExampleLogic-class 在其自己的 class 中创建服务。正如您在示例中看到的那样,我可以通过从 test-class 设置异步服务来使其工作。但是后来我只需要来自 Mockito 的 @Mock
。
它有效,因此这个问题更多是出于好奇和一点实用性(因为感觉没有必要为异步服务设置 setter 只是为了测试)。
所以问题是:
为什么会这样?
其他问题:
有什么办法吗?你推荐另一种测试方法吗?
希望有任何 GWT 专家可以帮助我!
使用:
JUnit 4.13
GwtMockito 1.1.9(以及后面的 Mockito:0.9.2)
ExampleLogic.java(Class 使用异步服务进行服务器调用)
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(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(用于创建异步实例的 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);
}
}
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; } }
您的猜测是正确的 - 这是您的问题。由于这个 Util.instance 字段是静态的,并且在测试完成后没有任何东西将其清空,因此下一次调用 Util.getInstance() 必须始终 return 相同的值,因此模拟永远不会已创建。
一些可能的考虑选项:
首先,以这种方式创建服务的成本非常低,有可能所有的服务调用都将被重新制作成静态方法,因此创建服务或保留实例几乎没有实际成本大约。这意味着您甚至可以在每次调用该方法时创建一个新的服务实例,也许每次调用 any 服务方法时。单例应该用于状态共享很重要的地方,或者创建成本很高的地方 - 至少从这里共享的代码来看,这些都不是真的。
由此扩展,您还可以根据需要直接 GWT.create(...) 服务,而不是保存实例的 Util 类型。
或者,假设您确实想要控制对实例的访问(可能允许在创建服务时自定义配置等),而不是 class 中的静态字段来保存为此,请考虑一个常规字段,以便可以将新的持有者实例化为每个测试的一部分。如果您不想要某种完整的 DI 工具,您仍然可以创建一个实例来提供这些对象。有一个只测试方法,在 tearDown() 等期间清空实例
依赖注入(“DI”)工具的 GWT 选项快速总结:
- Gin: In GWT2, "gin+guice" 非常流行,但不再维护,并且不会与 J2CL(另一个可以处理大部分 GWT2 输入的编译器)兼容,但非常灵活。Gin 是一个可以在 GWT 中工作的 Guice 功能的子集。
- 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 除了它的其他功能外,还可以充当 CDI 容器,而且还添加了许多其他功能 - 几乎肯定对您来说太过分了。对于今天开始的新项目,我不会考虑它。
- Crysknife 是另一种选择,专门为在 j2cl 中工作而构建,但仍在进行中(尽管工件作为 v0.1 发布到 Maven Central)。这设计得好像它只是 Errai 的 CDI 功能,而且更轻量级。