使用java创建PACT我无法在body

时间:2018-01-02 22:58:23

标签: java pact pact-jvm pact-java

我正在学习如何将PACT用于我的Java项目,我想在预期的输出上定义一些值限制。例如,在一个请求中/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?

1 个答案:

答案 0 :(得分:3)

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

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

它会覆盖调用$.id方法时创建的.numberType("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一起使用。

最后值得一提的是:合同测试和功能测试之间的区别

请记住,生成器仅用于生成在提供程序的合同验证测试运行时传递给提供程序的值。我创建的自定义生成器不会修改合同 - 它不会说只有0到100之间的值是正确的。当提供者执行合同验证时,它只会在此范围内生成一个值。因此,您的合同仍然适用于id,如1001或12700等。这很好,因为contract tests are not functional tests。消费者不应强迫这些业务规则。否则,您可以快速遇到consumerA表示id在0到100之间时是正确的情况,而consumerB表示id只有当它在99和之间时才正确999.我知道创建非常具体的合同很有吸引力,但这是一种过度规范的直接方式,可以保护供应商免受开发。希望它有所帮助。