“请求范围已经关闭”单元测试中的球衣错误

时间:2016-05-26 07:55:44

标签: java unit-testing junit jersey wiremock

我正在编写一个使用WireMock模拟资源的单元测试。我正在嘲笑我的端点抛出异常,例如:

    stubFor(
            post(urlEqualTo("/myEndpoint"))
            .willReturn(aResponse().withStatus(errorCode)
                    .withHeader("Content-Type", "application/json")
                    .withBody(errorJson)));

我的客户类的相关部分是在这里测试的:

import javax.ws.rs.client.Client;
import javax.ws.rs.client.WebTarget;

public MyClient() {

    private Client client;
    private String baseUrl;

    ...

    public MyDto createObject(MyDto myDto) throws ClientErrorException {
        String resourcePath = MessageFormat.format("myEndpoint");

        return client.target(baseUrl)
                .path(resourcePath)
                .request(MediaType.APPLICATION_JSON_TYPE)
                .header(CONTENT_TYPE_HEADER, MediaType.APPLICATION_JSON)
                .post(Entity.entity(myDto, MediaType.APPLICATION_JSON), MyDto.class);
    }
}

在我的单元测试中,我试图使用junit的ExpectedException来捕获并断言返回的错误,例如:

@Rule
public ExpectedException thrown = ExpectedException.none();

@Test
public void test_returnsError() {
    ...

    thrown.expect(NotAuthorizedException.class);
    thrown.expect(NotAuthorizedExceptionStatusMatcher.hasStatusAndError(401, UNAUTHORISED_ERROR));

    myClient.createObject(new MyDto());
}

NotAuthorizedExceptionStatusMatcher是我自己的自定义匹配器类:

import javax.ws.rs.NotAuthorizedException;
import org.hamcrest.Description;
import org.hamcrest.TypeSafeMatcher;

public class NotAuthorizedExceptionStatusMatcher extends TypeSafeMatcher<NotAuthorizedException> {

    public static NotAuthorizedExceptionStatusMatcher hasStatusAndError(int status, ErrorDescription entity) {
        return new NotAuthorizedExceptionStatusMatcher(status, entity);
    }

    private final int expectedStatus;
    private final ErrorDescription expectedError;

    private int actualStatus;
    private ErrorDescription actualError;

    private NotAuthorizedExceptionStatusMatcher(int expectedStatus, ErrorDescription expectedError) {
        this.expectedStatus = expectedStatus;
        this.expectedError = expectedError;
    }

    @Override
    public boolean matchesSafely(NotAuthorizedException exception) {
        actualStatus = exception.getResponse().getStatus();
        actualError = exception.getResponse().readEntity(ErrorDescription.class);
        return expectedStatus == actualStatus && expectedError.equals(actualError);
    }

    @Override
    public void describeTo(Description description) {
        description.appendValue(actualStatus)
                .appendText(" was found instead of ")
                .appendValue(expectedStatus)
                .appendText(" and ")
                .appendValue(actualError)
                .appendText(" was found instead of ")
                .appendValue(expectedError);
    }

}

当我的matcher尝试执行exception.getResponse()。readEntity(ErrorDescription.class)时,我收到错误:

java.lang.IllegalStateException: Request scope has been already shut down.
        at jersey.repackaged.com.google.common.base.Preconditions.checkState(Preconditions.java:149)
        at org.glassfish.jersey.process.internal.RequestScope.retrieveCurrent(RequestScope.java:239)
        at org.glassfish.jersey.process.internal.RequestScope.runInScope(RequestScope.java:416)
        at org.glassfish.jersey.client.InboundJaxrsResponse.readEntity(InboundJaxrsResponse.java:108)
        at MyProject.NotAuthorizedExceptionStatusMatcher.matchesSafely(NotAuthorizedExceptionStatusMatcher.java:28)
        at MyProject.NotAuthorizedExceptionStatusMatcher.matchesSafely(NotAuthorizedExceptionStatusMatcher.java:8)
        at org.hamcrest.TypeSafeMatcher.matches(TypeSafeMatcher.java:65)
        at org.hamcrest.core.AllOf.matches(AllOf.java:27)
        at org.hamcrest.DiagnosingMatcher.matches(DiagnosingMatcher.java:12)
        at org.junit.internal.matchers.StacktracePrintingMatcher.matchesSafely(StacktracePrintingMatcher.java:29)
        at org.junit.internal.matchers.StacktracePrintingMatcher.matchesSafely(StacktracePrintingMatcher.java:14)
        at org.hamcrest.TypeSafeMatcher.matches(TypeSafeMatcher.java:65)
        at org.hamcrest.MatcherAssert.assertThat(MatcherAssert.java:12)
        at org.junit.Assert.assertThat(Assert.java:956)
        at org.junit.Assert.assertThat(Assert.java:923)
        at org.junit.rules.ExpectedException.handleException(ExpectedException.java:252)
        at org.junit.rules.ExpectedException.access$000(ExpectedException.java:106)
        at org.junit.rules.ExpectedException$ExpectedExceptionStatement.evaluate(ExpectedException.java:241)
        at com.github.tomakehurst.wiremock.junit.WireMockRule$1.evaluate(WireMockRule.java:67)
        at org.junit.rules.RunRules.evaluate(RunRules.java:20)
        at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
        at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
        at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
        at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
        at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
        at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
        at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
        at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
        at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
        at org.apache.maven.surefire.junit4.JUnit4Provider.execute(JUnit4Provider.java:242)
        at org.apache.maven.surefire.junit4.JUnit4Provider.executeTestSet(JUnit4Provider.java:137)
        at org.apache.maven.surefire.junit4.JUnit4Provider.invoke(JUnit4Provider.java:112)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:497)
        at org.apache.maven.surefire.util.ReflectionUtils.invokeMethodWithArray(ReflectionUtils.java:189)
        at org.apache.maven.surefire.booter.ProviderFactory$ProviderProxy.invoke(ProviderFactory.java:165)
        at org.apache.maven.surefire.booter.ProviderFactory.invokeProvider(ProviderFactory.java:85)
        at org.apache.maven.surefire.booter.ForkedBooter.runSuitesInProcess(ForkedBooter.java:115)
        at org.apache.maven.surefire.booter.ForkedBooter.main(ForkedBooter.java:75)

如果我使用try-catch块来对异常进行断言,那么相同的代码似乎也有效,但我更喜欢ExpectedException方法。任何想法可能是什么原因在这里?我怀疑这是我正在使用的框架的组合,但我不太确定从哪里开始。

1 个答案:

答案 0 :(得分:0)

这可能是由测试类中规则的顺序引起的。这听起来很奇怪,但如果规则的顺序被解释为 [WireMock,ExpectedException] ,那么junit框架实际上是按照这个顺序构建的。规则被设计为测试周围的装饰器,这样每个规则都可以在任何@Before块之前和@After之后执行。

因此,在上面列出的示例中,解析规则 [WireMock,ExpectedException] ,执行顺序将为:

  1. WireMock启动服务器

  2. ExpectedException init(如果有的话)

  3. @Before execution

  4. 执行测试

  5. @After执行后

  6. ExpectedException评估和操作

  7. WireMock关闭服务器

  8. 我认为这个案例实际上就是您正在寻找的案例。

    如果我们切换WireMock和ExpectedException的顺序:

    1. ExpectedException init(如果有的话)

    2. WireMock启动服务器

    3. @Before execution

    4. 执行测试

    5. @After执行后

    6. WireMock关闭服务器

    7. ExpectedException评估和操作

    8. 然后,当ExpectedException规则被允许评估Exception中的Response时,Wiremock已经销毁了所有服务和状态。

      使用 RuleChain 可以保证您的规则排序,并解决问题(我能够模仿它)。

        

      交换 passOrder failOrder 链以查看测试失败!

      public class WiremockTest {
      
          public WireMockRule wireMockRule = new WireMockRule(8089);
          public ExpectedException thrown = ExpectedException.none();    
          @Rule
          public RuleChain passOrder = RuleChain.outerRule(wireMockRule).around(thrown);
          //public RuleChain failOrder = RuleChain.outerRule(thrown).around(wireMockRule);
      
          @Test
          public void exampleTestExpectConnectionException() throws Exception {
              thrown.expect(ConnectionObjectContainer.class);
      
              //Custom matcher that gets something from the cached connection after the failure occurs.
              thrown.expect(new BaseMatcher<ConnectionObjectContainer> (){
      
                  public boolean matches(Object item) {
                      ConnectionObjectContainer container = (ConnectionObjectContainer) item;
                      try {
                          //XXX This line is intended to mimic the NotAuthorizedException class behavior
                          System.out.println(container.getResponseCode());
      
                      } catch (IOException exception) {
                         throw new RuntimeException(exception);
                      }
                      return true;
                  }
      
                  public void describeTo(Description description) {
                      //Not used in sample
                  }});
      
      
              stubFor(get(urlPathMatching("/my/resource/[0-9]+")).willReturn(aResponse().withStatus(200).withHeader(
                  "Content-Type", "text/xml").withBody("<response>Some content</response>")));
      
              URL obj = new URL("http://localhost:8089/my/resource/121");
              HttpURLConnection con = (HttpURLConnection) obj.openConnection();
      
              // optional default is GET
              con.setRequestMethod("GET");
      
              throw new ConnectionObjectContainer(con);
          }
      
      
      
          /**
           * My Exception class that caches the HttpURLConnection and provides an accessor for the response code to the Matcher of my 
           * ExpectedException configuration.
           * 
           * @since Jun 17, 2016
           *
           */
          public class ConnectionObjectContainer extends Exception {
              HttpURLConnection connection;
              public ConnectionObjectContainer(HttpURLConnection conn) {
                  this.connection = conn;
              }
      
             public int getResponseCode() throws IOException {
                  return connection.getResponseCode();
              }
          }
      
      
      }
      
相关问题