Mockito:监视被测系统中依赖于 HTTP 请求的方法是不是一种不好的做法?

fir*_*lud 0 java testing unit-testing mockito

通过阅读周围的内容,当您必须监视您正在单元测试的当前方法所使用的方法时,这通常是不好的做法,并且是代码异味的迹象。

例如,我有一个正在单元测试的方法:

public MyResponseObject doStuff(MyRequestObject obj) {
    WebTarget tar = getServiceClient().target(obj.toString());
    Response res = tar.path(someURI).request().post(somejson);
    if(response.getStatus() == 200) {
        String jsonResp = response.readEntity(String.class);
        return convertToObj(jsonResp);
    }
}
Run Code Online (Sandbox Code Playgroud)

我尝试解决上述问题的一种方法是

  1. 将前两行(WebTarget、Response)提取到其自己的方法中,该方法返回 Response 对象。
  2. 创建 Response 的模拟并存根 readEntity 以返回 200 并存根 readEntity 以返回“OK”

结果如下:

public MyResponseObject doStuff(MyRequestObject obj) {
    Response res = sendRequest(obj.toString());
    if(response.getStatus() == 200) {
        String jsonResp = response.readEntity(String.class);
        return convertToObj(jsonResp);
    }
}

//extracted method
public Response sendRequest(String json){
    WebTarget tar = getServiceClient().target(someUrl);
    return res = tar.path(someURI).request().post(somejson);
}

//My unit test

//sut is the system under test, setup elsewhere
public void testDoStuff() {
    MyRequestObject request = ...;
    Response respMock = mock(Response.class);
    when(respMock.getStatus()).thenReturn(200);
    when(respoMock.readEntity()).thenReturn("OK");
    MyClass spy = spy(sut);
    Mockito.doReturn(respMock).when(spy).sendRequest(requestString);
    MyResponseObject  response = spy.doStuff(request);

    assertEquals(response.toString(),expectedResp);
}
Run Code Online (Sandbox Code Playgroud)

如果我不将其存根,它会尝试执行真正的 HTTP 请求并返回无效的 URL 错误,因为我没有提供真正的 URL 错误 - 我相信这就是我想要的,因为我希望我的单元测试独立于某些外部系统。

我应该有更好的方法来进行单元测试吗?

Joh*_*ger 5

是的,创建您正在测试的类的间谍是不好的做法,将您正在模拟的代码分解到另一个类中并模拟它,即:

public class MyClass {

    private final MySender sender;

    public MyClass() {
        this(new DefaultSender());
    }

    public MyClass(MySender sender) {
        this.sender = sender;
    }

    public MyResponseObject doStuff(MyRequestObject obj) {
        Response res = sender.sendRequest(obj.toString());
        if (response.getStatus() == 200) {
            String jsonResp = response.readEntity(String.class);
            return convertToObj(jsonResp);
        }
    }

    public interface MySender {
        Response sendRequest(String json);
    }

    private static class DefaultSender implements MySender {
        public Response sendRequest(String json) {
            WebTarget tar = getServiceClient().target(someUrl);
            return res = tar.path(someURI).request().post(somejson);
        }
    }
}

@RunWith(MockitoJUnitRunner.class)
public class MyClassTest {

    private MyClass testSubject;

    @Mock
    private MySender sender;

    @Mock
    private Response response;

    @Test
    public void testDoStuff() {
        String expectedResp = ...;
        MyRequestObject request = ...;
        MyResponseObject  response = testSubject.doStuff(request);

        assertEquals(response.toString(),expectedResp);
    }

    @Before
    public void setup() {
        testSubject = new MyClass(sender);
        when(sender.sendRequest(anyString()).thenReturn(response);
        when(response.getStatus()).thenReturn(200);
        when(response.readEntity()).thenReturn("OK");
    }
}
Run Code Online (Sandbox Code Playgroud)