如何使用Mockito测试REST服务?

28

我对Java单元测试非常陌生,听说Mockito框架非常适合测试目的。

我已经开发了一个REST服务器(CRUD方法),现在我想对其进行测试,但我不知道如何开始?

更重要的是,我不知道这个测试过程应该如何开始。我的服务器应该在本地主机上运行,并调用该URL(例如localhost:8888)吗?

这是我到目前为止尝试过的方法,但我相当确定这不是正确的方法。

    @Test
    public void testInitialize() {
        RESTfulGeneric rest = mock(RESTfulGeneric.class);

        ResponseBuilder builder = Response.status(Response.Status.OK);

        builder = Response.status(Response.Status.OK).entity(
                "Your schema was succesfully created!");

        when(rest.initialize(DatabaseSchema)).thenReturn(builder.build());

        String result = rest.initialize(DatabaseSchema).getEntity().toString();

        System.out.println("Here: " + result);

        assertEquals("Your schema was succesfully created!", result);

    }

这是 initialize 方法的代码。

    @POST
    @Produces(MediaType.APPLICATION_JSON)
    @Path("/initialize")
    public Response initialize(String DatabaseSchema) {

        /** Set the LogLevel to Info, severe, warning and info will be written */
        LOGGER.setLevel(Level.INFO);

        ResponseBuilder builder = Response.status(Response.Status.OK);

        LOGGER.info("POST/initialize - Initialize the " + user.getUserEmail()
                + " namespace with a database schema.");

        /** Get a handle on the datastore itself */
        DatastoreService datastore = DatastoreServiceFactory
                .getDatastoreService();


        datastore.put(dbSchema);

        builder = Response.status(Response.Status.OK).entity(
                "Your schema was succesfully created!");
        /** Send response */
        return builder.build();
    }

在这个测试用例中,我想将一个Json字符串发送到服务器(POST)。如果一切顺利,那么服务器应该以“Your schema was successfully created!”的回复。

有人可以帮忙吗?


1
在上面的代码中,您在哪里将JSON字符串发送到服务器?为什么您需要Mockito进行这样的测试? - JB Nizet
1
不,它并不是。rest只是一个模拟对象。它会回答你告诉它回答的内容。它并不是你的REST服务器。你正在测试Mockito而不是测试你的服务器。你不需要在这种测试中使用Mockito。 - JB Nizet
谢谢!你能指导我该怎么做吗?我想测试我的 CRUD 方法。我想要单元测试,以测试服务器上的每个 REST 方法。 - Ion Morozan
你可以进行集成测试,实际发送JSON数据到服务器并分析所得的答案,以确保它们是正确的;或者你可以进行单元测试,测试在服务器端使用的类的方法。Mockito对于后者非常有用。但没有看到你的代码,很难告诉你如何进行测试,除了指向Mockito文档。 - JB Nizet
我想建议使用WireMock。这是一个用于存根和模拟Web服务的库。http://wiremock.org/index.html - Lilylakshi
显示剩余4条评论
5个回答

26

好的。那么这个方法的契约是:将输入字符串解析为 JSON,如果无效则返回 BAD_REQUEST。如果有效,则在 datastore 中创建一个具有各种属性(你知道它们)的实体,并发送回OK

您需要验证此方法是否履行了该合同。

Mockito 在这里起到什么作用?如果您没有使用 Mockito 测试此方法,则需要一个真正的 DataStoreService,并且需要验证此真正的 DataStoreService 是否已正确创建实体。这就是您的测试不再是单元测试的地方,也是它过于复杂、过长和难以运行的地方,因为它需要一个复杂的环境。

通过模拟对 DataStoreService 的依赖关系,Mockito 可以帮助您:您可以创建一个 DataStoreService 的 mock,并在调用您的测试中的 initialize() 方法时验证该 mock 是否确实使用适当的实体参数调用。

为此,您需要能够将 DataStoreService 注入正在测试的对象中。可以通过以下方式轻松进行重构:

public class MyRestService {
    private DataStoreService dataStoreService;

    // constructor used on the server
    public MyRestService() {
        this.dataStoreService = DatastoreServiceFactory.getDatastoreService();
    }

    // constructor used by the unit tests
    public MyRestService(DataStoreService dataStoreService) {
        this.dataStoreService = dataStoreService;
    }

    public Response initialize(String DatabaseSchema) {
         ...
         // use this.dataStoreService instead of datastore
    }
}

现在在您的测试方法中,您可以执行以下操作:

@Test
public void testInitializeWithGoodInput() {
    DataStoreService mockDataStoreService = mock(DataStoreService.class);
    MyRestService service = new MyRestService(mockDataStoreService);
    String goodInput = "...";
    Response response = service.initialize(goodInput);
    assertEquals(Response.Status.OK, response.getStatus());

    ArgumentCaptor<Entity> argument = ArgumentCaptor.forClass(Entity.class);
    verify(mock).put(argument.capture());
    assertEquals("the correct kind", argument.getValue().getKind());
    // ... other assertions
}

谢谢!我有一个问题:这里应该是 verify(mockDataStoreService).put(argument.capture()); 吗?即使我改成了 mockDataStoreService,我仍然会得到一个错误 The method verify(DatastoreService) is undefined for the type MyRestService - Ion Morozan
1
测试方法应该在单元测试类中,而不是在被测试的类中。EasyMock通常与import static org.mockito.Mockito.*一起使用。verify是Mockito类的一个静态方法。 - JB Nizet

3
你所说的更像是集成测试,Mockito(或任何其他模拟框架)对你没有太大帮助。
如果你想对自己编写的代码进行单元测试,Mockito肯定是一个有用的工具。
我建议你阅读更多关于模拟/单元测试以及应该在哪些情况下使用它的信息。

我完全不同意这个答案。 我已经成功地在大量的集成测试中使用Mockito。通常,有一些整个子系统是无关紧要的,可以合理地进行模拟。无论何时你想测试系统的某一部分,而不受其他部分交互所干扰,Mockito(或其他模拟框架)通常都很有用。这可能是单元测试、量测试或集成测试。 - Dawood ibn Kareem
@DavidWallace 同意。我的措辞有误,我是指根据 OP 描述的情况(部署他的应用程序并向其发送 REST 请求),模拟将没有太大用处。 - darrengorman

2
Mockito主要用于测试代码的某些部分。例如,如果您正在使用REST服务,但不想进行完整的堆栈测试,则可以模拟连接到REST服务的服务,从而使您能够精确、一致地测试特定行为。
为了在不涉及数据库的情况下测试REST服务的内部部分(例如,特定的服务方法),您将模拟数据库子系统,从而只能测试服务的内部部分,而无需涉及数据库。这种测试应该属于REST服务模块,而不是客户端。
为了测试REST服务本身,您将使用实际的客户端库创建完整的堆栈集成测试。在此过程中,可以使用Mockito来模拟与REST服务消费无关的客户端的某些部分。

1
谢谢您的回复。实际上我需要测试一个我开发的REST服务,所以我想测试单个方法,但我不知道如何做。例如,正如我在问题中所说,我想向服务器发送JSON并验证响应。我不知道应该在本地主机还是已部署API的位置进行请求。我正在使用GAE作为后端。 - Ion Morozan
@IonMorozan 不太确定我理解问题的意思——你可以在本地进行测试,也可以在部署后进行测试,这取决于你想要测试哪个。无论是哪种情况,请求很可能都会在本地进行。 - Dave Newton

2

最佳方法是使用wiremock。添加以下依赖项:com.github.tomakehurst wiremock 2.4.1和org.igniterealtime.smack smack-core 4.0.6。

按照以下方式定义和使用wiremock:

@Rule
public WireMockRule wireMockRule = new WireMockRule(8089);

String response ="Hello world";
StubMapping responseValid = stubFor(get(urlEqualTo(url)).withHeader("Content-Type", equalTo("application/json"))
        .willReturn(aResponse().withStatus(200)
                .withHeader("Content-Type", "application/json").withBody(response)));

0

我同意这不是单元测试而是集成测试,不管怎样你最好看一下jersey和嵌入式grizzly服务器的测试。总之,这段代码启动了grizzly服务器(也可以启动数据库)在localhost:8888上,然后设置客户端的jersey客户端并发送一个POST请求,响应应该被测试。这是一个集成测试,因为你要同时测试服务器和数据库,你可以使用mockito来模拟数据库,但这取决于你的服务器和数据库有多紧密相关。

(使用jersey 1.11和grizzly 2.2进行测试)

    @BeforeClass
    public static void setUpClass() throws Exception {
        // starts grizzly
        Starter.start_grizzly(true);
        Thread.sleep(4000);
    }

    @Before
    public void setUp() throws Exception {
        client = new Client();
        webResource = client.resource("http://localhost:8888");
    }   

    @Test
    public void testPostSchemaDatabase() throws Exception {
        {
            String DatabaseSchema = "{ database_schema : {...}}";
            logger.info("REST client configured to send: "  + DatabaseSchema);
            ClientResponse response =  
                    webResource
                             .path("/initialize")
                             .type("application/json")
                             .post(ClientResponse.class, DatabaseSchema);
            //wait for the server to process
            Thread.sleep(2000);
            assertEquals(response.getStatus(), 204);    
            //test the response
        }       
    }

    @After
    public void after() throws JSONException
    {
            //probably you want to delete the schema at database and stop the server

}

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