使用 Mockito 和 Jersey 测试框架模拟 EJB
Mocking EJB's with Mockito and Jersey Test Framework
我能够使用球衣测试框架来测试我的 JAX-RS restful api。但是,无论我尝试什么,我都无法正确地模拟我的 EJB。
使用 @peeskillet 提供的示例,资源中引用我的 EJB 的点保持为空。
所以我知道这不是我的依赖。我相信问题是我嵌套了 EJB。我确定有几个因素可能会影响此代码的行为方式,但我不确定如何解决:
1. 我试图模拟的 ApplicationService 是一个接口,我有一个 ApplicationServiceImpl class,其中包含实际逻辑。由于我的测试并不关注该逻辑,因此我的思考过程是模拟接口应该就足够了。
2. 但是,该接口的实现还包含一个连接到 MongoDB 单例的 EJB。
所以我的问题是:
这些因素中的任何一个是否在这里发挥作用,为什么我的资源 class 中的接口引用始终为空,因此显然无法被模拟和注入?如果是这样,我该如何解决?
如果我需要切换到直接模拟实现而不是接口,那么我是否还需要模拟 DAO ejb?
需要说明的是,只要我不调用任何依赖于通过 EJB 注入的 fields/objects 的方法,测试框架就可以正常工作。
我的资源Class
@Path("/stats")
@Api(value = "stats", authorizations = {
@Authorization(value = "apiKey", scopes = {})
})
@Stateless
@Produces({MediaType.APPLICATION_JSON})
public class StatsResource {
private final static Logger LOG = Logger.getLogger(StatsResource.class.getName());
@EJB
ApplicationService applicationService;
public void setApplicationService(ApplicationService applicationService) {
this.applicationService = applicationService;
}
@GET
public String getHere(){
return "Got Here!";
}
@GET
@Path("test")
@ApiOperation(value = "Finds all applications",
notes = "Returns brief display which uses a limited subset of Application.class fields",
response = Application.class,
responseContainer = "List<Application>"
)
@JsonView(Views.Brief.class)
@Asynchronous
public void getApplications(@Suspended
final AsyncResponse asyncResponse) throws Exception {
asyncResponse.resume(doGetApplications());
}
private ApplicationList doGetApplications() throws Exception {
return new ApplicationList(applicationService.getAll());
}
}
我的测试用例
public class StatsResourceNGTest extends JerseyTestNg.ContainerPerMethodTest {
@Mock
private ApplicationService applicationService;
@Override
protected Application configure() {
enable(TestProperties.LOG_TRAFFIC);
enable(TestProperties.DUMP_ENTITY);
MockitoAnnotations.initMocks(this);
Logger.getLogger(StatsResourceNGTest.class.getName()).log(Level.INFO, "Mock App Service: {0}", applicationService.toString());
AbstractBinder binder = new AbstractBinder() {
@Override
protected void configure() {
bindFactory(MockApplicationServiceFactory.class)
.to(ApplicationResource.class);
}
};
ResourceConfig config = new IdentityRestApplication(true);
config.register(binder);
config.register(StatsResource.class);
return config;
}
@Test
public void assertMockInjectionWorked() throws Exception {
Response response = target("/stats/test").request(MediaType.APPLICATION_JSON).get();
verify(applicationService, times(1)).getAll();
Logger.getLogger(ApplicationResourceNGTest.class.getName()).log(Level.INFO, "Status: {0}", response.getStatus());
String msg = response.readEntity(String.class);
Logger.getLogger(ApplicationResourceNGTest.class.getName()).log(Level.INFO, "Response: {0}", msg);
Assert.assertNotNull(msg);
response.close();
}
}
我的 HK2 MockFactory
public class MockApplicationServiceFactory implements Factory<ApplicationService> {
private List<Application> appList;
@Override
public ApplicationService provide() {
appList = new ArrayList<>();
for (int i = 1; i < 6; i++) {
String appname = "App " + i;
Application app = new ApplicationBuilder()
.setName(appname)
.setDescription(appname + " Description")
.setTenantId(new ObjectId())
.createApplication();
appList.add(app);
}
try {
final ApplicationService mockedService = Mockito.mock(ApplicationService.class);
Mockito.when(mockedService.getAll()).thenReturn(appList);
return mockedService;
} catch (ApplicationException ex) {
Logger.getLogger(ApplicationResourceNGTest.class.getName()).log(Level.SEVERE, null, ex);
} catch (Exception ex) {
Logger.getLogger(ApplicationResourceNGTest.class.getName()).log(Level.SEVERE, null, ex);
}
return null;
}
@Override
public void dispose(ApplicationService t) {
}
}
@Mock
private ApplicationService applicationService;
和
final ApplicationService mockedService = Mockito.mock(ApplicationService.class);
有 2 个不同的 mock 实例。因此,您不能使用一个实例并检查另一个实例。
你需要找到一种方法来初始化模拟一次并在对象之间共享实例。
我真的没有举出最好的例子。虽然,Lars Juel Jensen's answer 使用 TestNG,而不是 JUnit,但它的设置更简洁并提供更好的可测试性
您可以执行以下操作。由于 ,ApplicationService
是不同的实例。相反,您可以做的是完全忘记 Factory
。只需使用模拟字段
@Mock
private ApplicationService applicationService;
@Override
protected Application configure() {
MockitoAnnotations.initMocks(this);
...
}
那就忘了AbstractBinder
吧。原因是它只适用于它知道的注解(即它不知道 @EJB
),除非你创建一些其他组件来让注解知道(但这太多了)。所以只需使用你的setter注入它
StatsResource resource = new StatsResource(applicationService);
config.register(resource);
现在,在每个 @Test
案例中,您可以提供完全不同的模拟实现,这意味着您的 wheh(..).then(..)
s 对于每个测试案例都可以不同。它比在工厂中保持相同的实现更灵活。
所以在您的 @Test
方法中,只需完成测试 class' applicationService
.
我能够使用球衣测试框架来测试我的 JAX-RS restful api。但是,无论我尝试什么,我都无法正确地模拟我的 EJB。
使用 @peeskillet 提供的示例,资源中引用我的 EJB 的点保持为空。
所以我知道这不是我的依赖。我相信问题是我嵌套了 EJB。我确定有几个因素可能会影响此代码的行为方式,但我不确定如何解决: 1. 我试图模拟的 ApplicationService 是一个接口,我有一个 ApplicationServiceImpl class,其中包含实际逻辑。由于我的测试并不关注该逻辑,因此我的思考过程是模拟接口应该就足够了。 2. 但是,该接口的实现还包含一个连接到 MongoDB 单例的 EJB。
所以我的问题是: 这些因素中的任何一个是否在这里发挥作用,为什么我的资源 class 中的接口引用始终为空,因此显然无法被模拟和注入?如果是这样,我该如何解决?
如果我需要切换到直接模拟实现而不是接口,那么我是否还需要模拟 DAO ejb?
需要说明的是,只要我不调用任何依赖于通过 EJB 注入的 fields/objects 的方法,测试框架就可以正常工作。
我的资源Class
@Path("/stats")
@Api(value = "stats", authorizations = {
@Authorization(value = "apiKey", scopes = {})
})
@Stateless
@Produces({MediaType.APPLICATION_JSON})
public class StatsResource {
private final static Logger LOG = Logger.getLogger(StatsResource.class.getName());
@EJB
ApplicationService applicationService;
public void setApplicationService(ApplicationService applicationService) {
this.applicationService = applicationService;
}
@GET
public String getHere(){
return "Got Here!";
}
@GET
@Path("test")
@ApiOperation(value = "Finds all applications",
notes = "Returns brief display which uses a limited subset of Application.class fields",
response = Application.class,
responseContainer = "List<Application>"
)
@JsonView(Views.Brief.class)
@Asynchronous
public void getApplications(@Suspended
final AsyncResponse asyncResponse) throws Exception {
asyncResponse.resume(doGetApplications());
}
private ApplicationList doGetApplications() throws Exception {
return new ApplicationList(applicationService.getAll());
}
}
我的测试用例
public class StatsResourceNGTest extends JerseyTestNg.ContainerPerMethodTest {
@Mock
private ApplicationService applicationService;
@Override
protected Application configure() {
enable(TestProperties.LOG_TRAFFIC);
enable(TestProperties.DUMP_ENTITY);
MockitoAnnotations.initMocks(this);
Logger.getLogger(StatsResourceNGTest.class.getName()).log(Level.INFO, "Mock App Service: {0}", applicationService.toString());
AbstractBinder binder = new AbstractBinder() {
@Override
protected void configure() {
bindFactory(MockApplicationServiceFactory.class)
.to(ApplicationResource.class);
}
};
ResourceConfig config = new IdentityRestApplication(true);
config.register(binder);
config.register(StatsResource.class);
return config;
}
@Test
public void assertMockInjectionWorked() throws Exception {
Response response = target("/stats/test").request(MediaType.APPLICATION_JSON).get();
verify(applicationService, times(1)).getAll();
Logger.getLogger(ApplicationResourceNGTest.class.getName()).log(Level.INFO, "Status: {0}", response.getStatus());
String msg = response.readEntity(String.class);
Logger.getLogger(ApplicationResourceNGTest.class.getName()).log(Level.INFO, "Response: {0}", msg);
Assert.assertNotNull(msg);
response.close();
}
}
我的 HK2 MockFactory
public class MockApplicationServiceFactory implements Factory<ApplicationService> {
private List<Application> appList;
@Override
public ApplicationService provide() {
appList = new ArrayList<>();
for (int i = 1; i < 6; i++) {
String appname = "App " + i;
Application app = new ApplicationBuilder()
.setName(appname)
.setDescription(appname + " Description")
.setTenantId(new ObjectId())
.createApplication();
appList.add(app);
}
try {
final ApplicationService mockedService = Mockito.mock(ApplicationService.class);
Mockito.when(mockedService.getAll()).thenReturn(appList);
return mockedService;
} catch (ApplicationException ex) {
Logger.getLogger(ApplicationResourceNGTest.class.getName()).log(Level.SEVERE, null, ex);
} catch (Exception ex) {
Logger.getLogger(ApplicationResourceNGTest.class.getName()).log(Level.SEVERE, null, ex);
}
return null;
}
@Override
public void dispose(ApplicationService t) {
}
}
@Mock
private ApplicationService applicationService;
和
final ApplicationService mockedService = Mockito.mock(ApplicationService.class);
有 2 个不同的 mock 实例。因此,您不能使用一个实例并检查另一个实例。
你需要找到一种方法来初始化模拟一次并在对象之间共享实例。
我真的没有举出最好的例子。虽然,Lars Juel Jensen's answer 使用 TestNG,而不是 JUnit,但它的设置更简洁并提供更好的可测试性
您可以执行以下操作。由于 ApplicationService
是不同的实例。相反,您可以做的是完全忘记 Factory
。只需使用模拟字段
@Mock
private ApplicationService applicationService;
@Override
protected Application configure() {
MockitoAnnotations.initMocks(this);
...
}
那就忘了AbstractBinder
吧。原因是它只适用于它知道的注解(即它不知道 @EJB
),除非你创建一些其他组件来让注解知道(但这太多了)。所以只需使用你的setter注入它
StatsResource resource = new StatsResource(applicationService);
config.register(resource);
现在,在每个 @Test
案例中,您可以提供完全不同的模拟实现,这意味着您的 wheh(..).then(..)
s 对于每个测试案例都可以不同。它比在工厂中保持相同的实现更灵活。
所以在您的 @Test
方法中,只需完成测试 class' applicationService
.