如何在 JUnit 5 测试中使用 WireMock 的响应模板

How to use WireMock's response template in JUnit 5 Tests

我正在尝试使用 WireMock 的 Response Templating 功能,但它似乎不适用于文档中提供的示例代码。

这是一段示例代码:


import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
import static com.github.tomakehurst.wiremock.client.WireMock.get;
import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo;
import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options;

import com.github.tomakehurst.wiremock.WireMockServer;
import com.github.tomakehurst.wiremock.extension.responsetemplating.ResponseTemplateTransformer;
import com.github.tomakehurst.wiremock.junit.WireMockRule;
import io.restassured.RestAssured;
import org.hamcrest.Matchers;
import org.junit.Rule;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

public class WireMockTest {

  @Rule
  public WireMockRule wm = new WireMockRule(options()
      .extensions(new ResponseTemplateTransformer(true)));
  private WireMockServer wireMockServer;

  @BeforeEach
  public void setup() {
    this.wireMockServer = new WireMockServer(
        options().port(8081));
    this.wireMockServer.stubFor(get(urlEqualTo("/test-url"))
        .willReturn(aResponse()
            .withBody("{{request.url}}")
            .withTransformers("response-template")));
    this.wireMockServer.start();
  }

  @Test
  public void test() {
    RestAssured.when()
        .get("http://localhost:8081/test-url")
        .then()
        .log().ifError()
        .body(Matchers.equalTo("/test-url"));
  }

  @AfterEach
  public void tearDown() {
    wireMockServer.stop();
  }
}

预期输出:

测试应该通过。 (这意味着 {{request.url}} 应该替换为 /test-url 作为模板渲染的结果)。

实际输出:

....

java.lang.AssertionError: 1 expectation failed.
Response body doesn't match expectation.
Expected: "/test-url"
  Actual: {{request.url}}

我尝试过的事情:

  1. 由于这些是使用 JUnit 5 API 的测试用例,因此没有添加 @Rule WireMockRule,而是添加了 .withTransformers("response-template")
  2. 尝试更改测试用例以使用 JUnit 4 API,并添加了
@Rule
public WireMockRule wm = new WireMockRule(options()
    .extensions(new ResponseTemplateTransformer(false))
);

(连同 withTransformers
3.将WireMockRule改为

@Rule
public WireMockRule wm = new WireMockRule(options()
    .extensions(new ResponseTemplateTransformer(true))
);

(连同变形金刚)
4. 删除了withTransformers,只保留WireMockRule。 (JUnit 4)
5. 我也尝试过上述与 JUnit 5 的组合 API。

但是 none 以上变体有效。有什么我想念的吗?

@Rule 方法将不起作用,因为您在 @BeforeEach 中自己创建新规则时忽略了 WireMockServer created/managed 规则。

您应该删除规则并通过 Options 对象将 @BeforeEach 中的 ResponseTemplateTransformer 添加到 WireMockServer

像这样的东西应该可以解决问题(从 Javadoc 判断)。

@BeforeEach
  public void setup() {
    this.wireMockServer = new WireMockServer(
        options()
          .extensions(new ResponseTemplateTransformer(false))
          .port(8081));
    this.wireMockServer.stubFor(get(urlEqualTo("/test-url"))
        .willReturn(aResponse()
            .withBody("{{request.url}}")
            .withTransformers("response-template")));
    this.wireMockServer.start();
  }

您可以编写自己的 Junit Jupiter 扩展。

举个例子

public class WireMockJunitJupiterExtension implements BeforeAllCallback, AfterAllCallback, ExecutionCondition {
    private static final ExtensionContext.Namespace NAMESPACE = create(WireMockJunitJupiterExtension.class);
    private static final String WIREMOCK = "wiremock";

    @Override
    public void afterAll(ExtensionContext context) {
        final var server = context.getStore(NAMESPACE).get("wiremock", WireMockServer.class);
        server.stop();
        server.resetAll();
    }

    @Override
    public void beforeAll(ExtensionContext context) {
        final var store = context.getStore(NAMESPACE);
        final var server = new WireMockServer(context.getRequiredTestClass().getAnnotation(WireMock.class).port());
        findFields(context.getRequiredTestClass(), field -> field.isAnnotationPresent(WithWireMock.class), TOP_DOWN)
                .stream()
                .map(field -> tryToReadFieldValue(field)
                        .andThen(o -> call(() -> (MappingBuilder) o))
                        .getOrThrow(RuntimeException::new)
                ).forEach(server::stubFor);
        store.put(WIREMOCK, server);
        server.start();
    }

    @Override
    public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) {
        final var shouldRun = context.getRequiredTestClass().isAnnotationPresent(WireMock.class);
            return shouldRun ? enabled("Started WireMock server") : disabled("Skipped starting WireMock server");
    }
}

注释

@Retention(RetentionPolicy.RUNTIME)
@ExtendWith(WireMockJunitJupiterExtension.class)
public @interface WireMock {
    int port();
}
@Retention(RetentionPolicy.RUNTIME)
public @interface WithWireMock {
}

以及如何使用它

@WireMock(port = 8077)
public class EventsExporterITTest {
    @WithWireMock
    static MappingBuilder mappingBuilder = post(urlPathEqualTo("path_uri"))
            .withRequestBody(equalToJson(readJsonFromFile("file.json")))
            .willReturn(aResponse()
                    .withStatus(200)
                    .withHeader("Content-Type", "application/json"));
    
    @Test
    public void someTest() {
    }
}

汇总了您的所有答案和建议,找到了最适合我的选择:

@Configuration
public class WireMockJunit5Extension implements BeforeAllCallback, AfterAllCallback {

    WireMockServer wireMockServer = new WireMockServer();

    @Override
    public void beforeAll(ExtensionContext context) {

        wireMockServer.stubFor(post("/{url1}")
                .willReturn(aResponse().withStatus(200)
                        .withHeader(CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
                        .withBody("{\"status\": \"ok\"}")));

        wireMockServer.stubFor(post("/{url2}")
                .willReturn(aResponse().withStatus(200)
                        .withHeader(CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
                        .withBody("{\"status\": \"ok\"}")));

        wireMockServer.start();
    }

    @Override
    public void afterAll(ExtensionContext context) {
        wireMockServer.stop();
        wireMockServer.resetAll();
    }
}

2021 年更新,看起来相当于为 JUnit 5 用户创建了 WireMockRule。

class MyTest {
    @RegisterExtension
    static WireMockExtension wm = WireMockExtension.newInstance()
            .options(wireMockConfig()
                 .dynamicPort()
                 .dynamicHttpsPort()
                 .extension(...))
            .build();

    @Test
    testFoo() {
      vm.stubFor(...);
      doSomethingUsingUrl(vm.getRuntimeInfo.getHttpBaseUrl() + "/test")
      vm.verify(...);
    }
}

(wm.getRuntimeInfo().getHttpBaseUrl() 包括 wm.getRuntimeInfo().getHttpPort() - 是的)

请参阅此处的 Advanced usage - programmatic 部分:

http://wiremock.org/docs/junit-jupiter/