使用Mockito和Jersey测试框架模拟EJB。

3
我能够使用jersey测试框架来测试我的JAX-RS restful api。但是,无论我尝试什么方法,都不能正确模拟我的EJB。
使用@peeskillet提供的示例,我的Resource中引用EJB的点仍然为null。
因此,我知道这不是我的依赖关系的问题。我相信问题在于我有嵌套的EJB。有一些因素可能会影响代码的行为,但我不确定如何解决:
1. 我要模拟的ApplicationService是一个接口,我有一个ApplicationServiceImpl类,其中包含实际逻辑。由于我的测试并不关注该逻辑,所以我认为模拟接口应该足够了。 2. 但是,接口的实现也包含一个连接到MongoDB单例的EJB。
所以我的问题是: 以上两个因素是否在这里起作用,导致我的资源类中的接口引用始终为空,从而明显地无法被模拟和注入?如果是这样,我该如何解决?
如果我需要切换到直接模拟实现而不是接口,那么我是否还需要模拟DAO EJB?
需要明确的是,只要我不调用通过EJB注入的字段/对象来依赖任何方法,测试框架就完全正常工作。
我的Resource类:
    @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();

    }

}

My 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) {
}

}


--已删除--位置不正确。 - Jenn Sears
2个回答

1
@Mock
private ApplicationService applicationService;

并且

final ApplicationService mockedService = Mockito.mock(ApplicationService.class);

这里有两个不同的模拟实例。因此,您不能使用一个实例并检查另一个实例。

您需要找到一种方法来初始化模拟实例并在对象之间共享该实例。


谢谢。我之前没注意到这个问题。我尝试了很多不同的方法,有些残留下来的东西是之前尝试的结果。但问题仍未解决。 - Jenn Sears

1
我并没有给出最好的例子。虽然Lars Juel Jensen的回答使用的是TestNG而不是JUnit,但它的设置更加清晰,并且提供了更好的可测试性。
你可以这样做。正如juherr提到的那样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(..)对于每个测试案例都可以是不同的。这比在工厂中保持相同的实现更加灵活。因此,在您的@Test方法中,只需使用测试类的applicationService即可。

谢谢,看起来它正在工作。 以前当我尝试仅使用mockito注释时,它似乎无法正常工作。 这次,我通过确保我的验证语句失败来测试它。 我要求它检查并查看我在applicationService上模拟的方法是否被调用了两次。 它至少被调用了一次,所以我认为这是一个胜利。 然而,grizzly返回的是500而不是200,所以我想这是需要修复的另一件事情。 谢谢! - Jenn Sears
@peeskillet 你有没有使用内存数据库和可嵌入的EJB容器的示例或经验? - Jin Kwon

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接