模拟RxJava异步http调用

时间:2016-12-01 08:00:28

标签: java junit mockito rx-java

我在模拟一个进行HTTP调用的RxJava函数时遇到了一些麻烦。我正在使用JUnit和Mockito。

//Customer.java extends ServiceManager
public Observable<String> getCustomerDetails(String uuidData){
    String api = "http://someapi.com/" + uuidData;
    return callHttps(api, getHeader(),
            "",
            HttpMethod.GET)
            .doOnError(failure -> logger.error("Error was raised while calling Profile Save of ORCH API:"
                    + failure.getMessage()))
            .doOnNext(httpClientResponse -> {
                logger.info("");
            })
            .concatMap(RxHelper::toObservable)
            .reduce(Buffer.buffer(), Buffer::appendBuffer)
            .map(buffer -> buffer.toString("UTF-8"))
            .map(entries -> entries);
}

private MultiMap getProfileHeader(){
    MultiMap headers = MultiMap.caseInsensitiveMultiMap();
    headers.add("Accept","application/json");
    return headers;
}


public class ServiceManager {
    @Inject
    @Named("httpsClient")
    private HttpClient httpsClient;

    private static final Logger logger = LoggerFactory.getLogger(ServiceManager.class);

    public Observable<HttpClientResponse> callHttps(String url, MultiMap headers, String body, HttpMethod httpMethod) {
        return Observable.create(subscriber -> {
            HttpClientRequest httpClientRequest = httpsClient.requestAbs(httpMethod, url);
            httpClientRequest.exceptionHandler(event -> {
               logger.error("Exception was raised :" + event.getMessage());
            });
            httpClientRequest.headers().addAll(headers);
            RxHelper
                    .toObservable(httpClientRequest)
                    .subscribe(subscriber);

            httpClientRequest.end(body);
        });
    }
}

如何模拟callHttps函数,以便它返回HttpClientRequest模拟的响应。对我来说另一种方法是使用WireMock,但我想通过模拟上面的函数找到一种方法。

2 个答案:

答案 0 :(得分:0)

假设Customer不必是最终版,您可以使用Self-Shunt Pattern。基本上,在此模式中,您将扩展您的测试类以使用新的模拟功能覆盖方法。因此,您将扩展Customer并覆盖callHttps(),以便它实际上不会执行任何操作,除非记录它被调用,以便您可以验证它是否实际被调用。

请注意,我并不是说你应该像这样测试你的代码。通常使用此模式表示code that could be restructured。所以,如果可以的话,完全放弃继承。话虽如此,如果必须,请尝试使用类似于以下示例代码的内容:

public class CustomerTest {
  @Mock
  private Observable<HttpClientResponse> mockObservable;

  @Before
  public void setup() {
    MockitoAnnotations.initMocks(this);
  }

  @Test
  public void getCustomerDetailsCallsCallHttps() {
    CustomerSelfShunt customerUnderTest = new CustomerSelfShunt();
    // Call the getCustomerDetails method. Should call overridden version of
    // callHttps() that sets callHttpsInvoked.
    Observable<String> actualObservable = customerUnderTest.getCustomerDetails("foo");
    assertEquals(this.mockObservable, actualObservable);
    assertTrue(customerUnderTest.callHttpsInvoked);
  }

  private class CustomerSelfShunt extends Customer {
    boolean callHttpsInvoked = false;

    public Observable<HttpClientResponse> callHttps(String url, MultiMap headers, String body, HttpMethod httpMethod) {
      // do nothing implementation, just record that this method was called.
      callHttpsInvoked = true;
    }
  }
}

答案 1 :(得分:0)

一些选项:

  1. Mockito提供间谍设施来协助解决这种情况。 (虽然它的使用通常表示应该重组的代码)
  2. 示例(简化 - 在这种情况下我不得不模拟HttpClientResponse,因为我无法创建具体的实例 - 但至少你避免模拟Observable):

    package com.sbp;
    
    import static org.junit.Assert.assertEquals;
    import static org.mockito.Matchers.any;
    import static org.mockito.Matchers.anyString;
    import static org.mockito.Mockito.mock;
    import static org.mockito.Mockito.when;
    import static rx.Observable.just;
    
    import io.vertx.core.MultiMap;
    import io.vertx.core.http.HttpClientResponse;
    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.mockito.Spy;
    import org.mockito.runners.MockitoJUnitRunner;
    import org.springframework.http.HttpMethod;
    import rx.Observable;
    
    @RunWith(MockitoJUnitRunner.class)
    public class CustomerTest {
    
        public static class ServiceManager {
    
            public Observable<HttpClientResponse> callHttps(String url, MultiMap headers, String body,
                    HttpMethod httpMethod) {
                return null;
            }
        }
    
        public static class Customer extends ServiceManager {
    
            public Observable<String> getCustomerDetails(String uuidData) {
                return callHttps("", null, null, null).map(r -> r.getHeader(""));
            }
        }
    
        @Spy
        private Customer customer;
    
        @Test
        public void getCustomerDetailsCallsCallHttps() {
            HttpClientResponse mockResponse = mock(HttpClientResponse.class);
            when(mockResponse.getHeader(anyString())).thenReturn("test");
            when(customer.callHttps(any(), any(), any(), any())).thenReturn(just(mockResponse));
    
            String headerValue = customer.getCustomerDetails("uuid").toBlocking().single();
    
            assertEquals("test", headerValue);
        }
    }
    
    1. 子类和覆盖 - 这是一篇优秀的文章,源自测试遗留代码簿,解释了该方法以及何时适合使用 - PHP Quickstart

    2. 中断继承模型并将ServiceManager实例注入Customer实例。然后,您将能够使用标准Mockito构造在Customer测试中模拟ServiceManager - 并且 - 您将能够独立于其子类测试ServiceManager。