使用 java 创建 PACT 我无法在正文中设置 numberType 的最小值

Using java to create PACT I am not able to set the min value of the numberType in the body

我正在学习如何在我的 Java 项目中使用 PACT,我想对预期输出定义一些值限制。例如,在一个请求中 /hello-world 我希望在 id 属性中收到一个数字,该数字应始终大于零。

package com.thiagomata.pact.hello.consumer.consumer;

import au.com.dius.pact.consumer.ConsumerPactBuilder;
import au.com.dius.pact.consumer.PactVerificationResult;
import static io.pactfoundation.consumer.dsl.LambdaDsl.newJsonBody;
import au.com.dius.pact.consumer.dsl.PactDslJsonBody;
import au.com.dius.pact.model.MockProviderConfig;
import au.com.dius.pact.model.RequestResponsePact;
import com.thiagomata.pact.hello.consumer.models.Greeting;
import io.pactfoundation.consumer.dsl.LambdaDslJsonBody;
import org.junit.Assert;
import org.junit.Test;
import scala.tools.jline_embedded.internal.Log;

import static au.com.dius.pact.consumer.ConsumerPactRunnerKt.runConsumerTest;
import static org.junit.Assert.assertEquals;

public class NameApplicationPactTest {

    @Test
    public void testNamePact() throws Throwable {

        Log.debug("inside the test");
        /**
         * Creating the mock server
         *
         * Define the expected input
         *  Using relative address
         *  The provider address will be automatically created
         *  The provider port will be automatically created
         * Define the expected output
         *  Keep the id as a undefined integer
         *  Set the content to the test
         */
        RequestResponsePact pact = ConsumerPactBuilder
            .consumer("hello_world_consumer")
            .hasPactWith("hello_world_provider")
            .uponReceiving("a request of hello world")
            .path("/hello-world")
            .matchQuery("name","johny")
            .method("GET")
            .willRespondWith()
            .body(
                newJsonBody( (LambdaDslJsonBody o) -> o.
                    numberType("id"). // <====================================
                    stringType("content", "Hello johny")
                ).build()
            )
            .toPact();

        /**
         * Let the Pact define the mock server address and port
         */
        MockProviderConfig config = MockProviderConfig.createDefault();

        /**
         * Create the mock server into the defined config and with the
         * pact result prepared
         */
        PactVerificationResult result = runConsumerTest(
            pact,
            config,
            mockServer -> {

                Log.debug("inside mock server");
                /**
                 * Pass the mock server configuration to the consumer classes
                 */
                DummyConsumer consumer = new DummyConsumer(
                    mockServer.getUrl(),
                    mockServer.getPort(),
                    "johny"
                );

                /**
                 * Now, when the code internally fires to the
                 * mockServer we should get the expected answer
                 */
                Greeting greeting = consumer.getGreeting();

                Log.debug(greeting);

                Assert.assertNotNull(
                    "Greeting id should not be null",
                    greeting.getId()
                );

                /**
                 * Currently I am not able to define a rule into the
                 * DSL Matching methods to assure that the value should
                 * be bigger than 0
                 */
                Assert.assertTrue( greeting.getId() > 0 ); // <=================================================

                assertEquals(
                    "Validate expected default greeting content",
                    "Hello johny",
                    greeting.getContent()
                );

                Log.debug("status code = " + consumer.getStatusCode() );

                Assert.assertTrue(
                    "test consumer status code",
                    consumer.getStatusCode().equals(
                        200
                    )
                );
            }
        );

        /**
         * If some Assert inside of the anonymous functions fails
         * it will not automatically throw a failure.
         *
         * We need to capture the error from the result
         */
        if (result instanceof PactVerificationResult.Error) {
            throw ((PactVerificationResult.Error) result).getError();
        }

        assertEquals(PactVerificationResult.Ok.INSTANCE, result);
    }
}

有人可能会说 PACT 无法应用此类限制。 但是,从生成的 PACT 来看,似乎可以在 PACT 中为生成器创建最小值和最大值:

    {
      "provider": {
        "name": "hello_world_provider"
      },
      "consumer": {
        "name": "hello_world_consumer"
      },
      "interactions": [
        {
          "description": "Test User Service",
          "request": {
            "method": "GET",
            "path": "/hello-world"
          },
          "response": {
            "status": 200,
            "headers": {
              "content-type": "application/json",
              "Content-Type": "application/json; charset\u003dUTF-8"
            },
            "body": {
              "id": 100,
              "content": "string"
            },
            "matchingRules": {
              "body": {
                "$.id": {
                  "matchers": [
                    {
                      "match": "integer"
                    }
                  ],
                  "combine": "AND"
                },
                "$.content": {
                  "matchers": [
                    {
                      "match": "type"
                    }
                  ],
                  "combine": "AND"
                }
              }
            },
            "generators": {
              "body": {
                "$.id": {
                  "type": "RandomInt",
                  "min": 0, /* <======================================== */
                  "max": 2147483647
                },
                "$.content": {
                  "type": "RandomString",
                  "size": 20
                }
              }
            }
          },
          "providerStates": [
            {
              "name": "default"
            }
          ]
        }
      ],
      "metadata": {
        "pact-specification": {
          "version": "3.0.0"
        },
        "pact-jvm": {
          "version": "3.5.10"
        }
      }
    }

我试图找到一些方法,查看 PACT 代码。因此,按照 numberType 方法的轨迹, LambdaDsl:

/* ... */

public LambdaDslObject numberType(final String... names) {
    object.numberType(names);
    return this;
}

/* ... */

该方法使用以下可能的方法调用 object.numberTypes 进入 LambdaDslJsonBody

/**
 * Attribute that can be any number
 * @param name attribute name
 */
public PactDslJsonBody numberType(String name) {
    generators.addGenerator(
        Category.BODY, 
        matcherKey(name), 
        new RandomIntGenerator(0, Integer.MAX_VALUE) // <========================
    );
    return numberType(name, 100);
}

/**
 * Attributes that can be any number
 * @param names attribute names
 */
public PactDslJsonBody numberType(String... names) {
  for (String name: names) {
    numberType(name);
  }
  return this;
}

/**
 * Attribute that can be any number
 * @param name attribute name
 * @param number example number to use for generated bodies
 */
public PactDslJsonBody numberType(String name, Number number) {
    body.put(name, number);
    matchers.addRule(matcherKey(name), new NumberTypeMatcher(NumberTypeMatcher.NumberType.NUMBER));
    return this;
}

只有一个生成器,始终从零开始。

那么,有一些可能的方法来为 PACT 创建这种随机生成器,以确保生成的随机数的值大于零或小于 100?

它是可行的(替换默认值生成器),但它需要应用一个小的解决方法。您可以将自定义生成器添加到 DslPart.getGenerators() 方法返回的生成器列表中,例如:

DslPart.getGenerators()
            .addGenerator(Category.BODY, ".id", new RandomIntGenerator(0, 100));

它将覆盖调用 .numberType("id") 方法时创建的 $.id 字段的生成器。看看这个典型的消费者合同测试:

import au.com.dius.pact.consumer.Pact;
import au.com.dius.pact.consumer.PactProviderRuleMk2;
import au.com.dius.pact.consumer.PactVerification;
import au.com.dius.pact.consumer.dsl.DslPart;
import au.com.dius.pact.consumer.dsl.PactDslJsonBody;
import au.com.dius.pact.consumer.dsl.PactDslWithProvider;
import au.com.dius.pact.model.RequestResponsePact;
import au.com.dius.pact.model.generators.Category;
import au.com.dius.pact.model.generators.RandomIntGenerator;
import org.codehaus.jackson.map.ObjectMapper;
import org.junit.Rule;
import org.junit.Test;

import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;

import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;

public class PactIntGeneratorTest {

    @Rule
    public PactProviderRuleMk2 mockProvider = new PactProviderRuleMk2("providerA", "localhost", 8080, this);

    @Pact(consumer = "consumerA", provider = "providerA")
    public RequestResponsePact requestA(PactDslWithProvider builder) throws Exception {
        final DslPart body = new PactDslJsonBody()
                .numberType("id")
                .stringType("content", "Hello johny");

        body.getGenerators()
                .addGenerator(Category.BODY, ".id", new RandomIntGenerator(0, 100));

        return builder
                .uponReceiving("(GET) /foo")
                    .path("/foo")
                    .method("GET")
                .willRespondWith()
                    .status(200)
                    .body(body)
                .toPact();
    }

    @Test
    @PactVerification(fragment = "requestA")
    public void testRequestA() throws IOException, InterruptedException {
        //given:
        final ObjectMapper objectMapper = new ObjectMapper();

        //when:
        final InputStream json = new URL("http://localhost:8080/foo").openConnection().getInputStream();
        final Map response = objectMapper.readValue(json, HashMap.class);

        //then:
        assertThat(((Integer) response.get("id")) > 0, is(true));
        //and:
        assertThat(response.get("content"), is(equalTo("Hello johny")));
    }
}

这不完全是您的情况,但它展示了如何覆盖 $.id 字段的生成器。在 运行 执行此测试后,将生成以下 Pact 文件:

{
    "provider": {
        "name": "providerA"
    },
    "consumer": {
        "name": "consumerA"
    },
    "interactions": [
        {
            "description": "(GET) /foo",
            "request": {
                "method": "GET",
                "path": "/foo"
            },
            "response": {
                "status": 200,
                "headers": {
                    "Content-Type": "application/json; charset=UTF-8"
                },
                "body": {
                    "id": 100,
                    "content": "Hello johny"
                },
                "matchingRules": {
                    "body": {
                        "$.id": {
                            "matchers": [
                                {
                                    "match": "number"
                                }
                            ],
                            "combine": "AND"
                        },
                        "$.content": {
                            "matchers": [
                                {
                                    "match": "type"
                                }
                            ],
                            "combine": "AND"
                        }
                    }
                },
                "generators": {
                    "body": {
                        "$.id": {
                            "type": "RandomInt",
                            "min": 0,
                            "max": 100
                        }
                    }
                }
            }
        }
    ],
    "metadata": {
        "pact-specification": {
            "version": "3.0.0"
        },
        "pact-jvm": {
            "version": "3.5.10"
        }
    }
}

如您所见,RandomIntGenerator 与属性 min:0max:100 一起使用。

最后值得一提的是:契约测试和功能测试的区别

请记住,当提供商的合同验证测试 运行ning 时,生成器仅用于生成传递给提供商的值。我创建的自定义生成器不会修改合同——它并没有说只有 0 到 100 之间的值才是正确的。它只会在提供者执行合约验证时生成此范围内的值。所以你的合同仍然对 id 有效,比如 1001 或 12700 等。这很好,因为 contract tests are not functional tests。消费者不应该强迫这些商业规则。否则你可能会很快 运行 陷入这样一种情况,即 consumerAid 在 0 到 100 之间时是正确的,而 consumerBid 只有在它在 99 到 999 之间。我知道创建非常具体的合同很诱人,但这是一种直接过度规范的方式,使供应商无法进行开发。希望对你有帮助。