Тестирование контракта потребителя сервиса — часть 3

от автора

Это третий блог из серии публикаций о тестировании контрактов  потребителей сервиса. Я представил концепцию в первом блоге. Второй блог посвящен написанию тестов с использованием Pact для синхронной коммуникации. В этом блоге мы рассмотрим, как писать тесты, когда среда коммуникации основана на сообщениях.

В нашем примере кредитный шлюз эмитирует событие о создании займа. Служба предоставления займов прослушивает его и выполняет дальнейшую обработку. В случае коммуникации на основе Http видно, что фреймворк Pact запускает имитатор Http-сервера. Коммуникация на основе сообщений отличается от Http тем, что не существует единого стандартного способа коммуникации. Она может быть организована с помощью различных инструментов, таких как Kafka, RabbitMQ, ActiveMQ и т.д. Pact может не связываться с этими инструментами, и, поэтому, он не запускает ни один из них во время выполнения тестов, а просто позволяет нам убедиться, что потребитель и производители событий придерживаются одной и той же схемы. В конечном итоге это то, что нам нужно! Давайте перейдем к коду.

Потребительский тест

Начнем с потребительского теста. В нашем примере листенер в службе предоставления займов является потребителем события, эмитируемого кредитным шлюзом. Ниже приведены шаги по созданию потребительских тестов и контракта.

1. Как обычно, начнем с теста spring boot. Поскольку класс LoanFulfilmentConsumer является потребителем в данном случае, мы напишем тест для него. На этот раз давайте сначала определим метод pact. Нам нужно создать MessagePact вместо RequestResponsePact. Ниже приведен метод.

 @Pact(consumer = "loan_fulfilment_service", provider = "loan_gateway") fun eventForLoanFulfilment(builder: MessagePactBuilder): MessagePact {    return builder        .expectsToReceive("Loan creation event")        .withMetadata(mapOf("traceId" to "1"))        .withContent(PactDslJsonBody()            .`object`("fraudCheck", PactDslJsonBody().booleanType("status"))            .stringType("customerId"))        .toPact() }

Метод не требует пояснений. Мы описываем в основном то, что содержит сообщение. Давайте разложим это по полочкам.

@Pact(consumer =»loan_fulfilment_service», provider = «loan_gateway»)

Имена провайдера и потребителя, они будут опубликованы вместе с контрактом

expectsToReceive(«Loan creation event»)

Это описание взаимодействия, провайдер должен предоставить пример события из метода, аннотированного этим описанием

withMetadata(mapOf(«traceId» to «1»))

Это опционально и обозначает, будет ли сообщение содержать метаданные или нет.

withContent(…)

Тело сообщения

2. Предполагается, что тестовый метод принимает сообщения. И нам нужно убедиться, что оно соответствует объекту.

@Test fun `should return false fraud status`(messages: List<Message>) {    messages.size shouldBe 1    jacksonObjectMapper().readValue(        messages[0].contents.valueAsString(),        LoanApplication::class.java) }

3. Необходимо явно указать, что провайдер будет асинхронным. Мы можем сделать это с помощью аннотации pactTestFor. Ниже приведен весь тест.

@ExtendWith(PactConsumerTestExt::class) @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT) @PactTestFor(providerType = ProviderType.ASYNCH) class LoanFulfilmentConsumerTest {    @Pact(consumer = "loan_fulfilment_service", provider = "loan_gateway")    fun eventForLoanFulfilment(builder: MessagePactBuilder): MessagePact {        return builder            .expectsToReceive("Loan creation event")            .withMetadata(mapOf("traceId" to "1"))            .withContent(PactDslJsonBody()                .`object`("fraudCheck", PactDslJsonBody().booleanType("status"))                .stringType("customerId"))            .toPact()    }     @Test    fun `should return false fraud status`(messages: List<Message>) {        messages.size shouldBe 1        jacksonObjectMapper().readValue(            messages[0].contents.valueAsString(),            LoanApplication::class.java        )    } }

Запуск этого теста создает контракт в папке target/pacts.

{  "consumer": {    "name": "loan_fulfilment_service"  },  "provider": {    "name": "loan_gateway"  },  "messages": [    {      "description": "Loan creation event",      "metaData": {        "traceId": "1",        "contentType": "application/json"      },      "contents": {        "customerId": "string",        "fraudCheck": {          "status": true        }      },      "matchingRules": {        "body": {          "$.fraudCheck.status": {            "matchers": [              {                "match": "type"              }            ],            "combine": "AND"          },          "$.customerId": {            "matchers": [              {                "match": "type"              }            ],            "combine": "AND"          }        }      }    }  ],  "metadata": {    "pactSpecification": {      "version": "3.0.0"    },    "pact-jvm": {      "version": "4.0.10"    }  } }

Тест провайдера

1. На стороне провайдера нам нужно указать пример события, которое соответствует схеме, предоставленной потребителем.

2. Давайте начнем с теста spring boot и добавим тест pact, как показано ниже. Мы настраиваем контекст с AmpqTestTarget.

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT) class LoanApplicationEventProviderTest {    @BeforeEach    fun setup(context: PactVerificationContext) {        context.target = AmpqTestTarget()    }     @TestTemplate    @ExtendWith(PactVerificationInvocationContextProvider::class)    fun pactVerificationTestTemplate(context: PactVerificationContext) {        context.verifyInteraction()    } }

3. Фреймворк Pact не знает, какие взаимодействия тестировать из этих pact файлов. Давайте предоставим эту информацию с помощью аннотаций.

@PactFolder("target/pacts") @Provider("loan_gateway")

4. Запустите тест. Тест завершается с исключением No annotated methods were found for interaction 'Loan creation event'. You need to provide a method annotated with @PactVerifyProvider("Loan creation event") on the classpath that returns the message contents. (Не найдено аннотированных методов для взаимодействия ‘Событие создания кредита’. Вам необходимо предоставить метод, аннотированный @PactVerifyProvider(«Событие создания кредита») в пути класса, который возвращает содержимое сообщения.)

5. Взглянув на строку ‘Loan creation event’, мы понимаем, что упомянули ее в выражении expectsToReceive. Давайте вспомним нашу задачу. Провайдер должен предоставить пример события из метода, аннотированного этим описанием. Добавим метод для предоставления примера события.

@PactVerifyProvider("Loan creation event") fun exampleEvent(): MessageAndMetadata {    val loanApplication = LoanApplication("1", FraudCheck(false))    val eventString = jacksonObjectMapper().writeValueAsString(loanApplication)    return MessageAndMetadata(eventString.toByteArray(), mapOf("traceId" to "1")) }

Ниже приведен весь тест.

@PactFolder("target/pacts") @Provider("loan_gateway") @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT) class LoanApplicationEventProviderTest {    @BeforeEach    fun setup(context: PactVerificationContext) {        context.target = AmpqTestTarget()    }     @TestTemplate    @ExtendWith(PactVerificationInvocationContextProvider::class)    fun pactVerificationTestTemplate(context: PactVerificationContext) {        context.verifyInteraction()    }     @PactVerifyProvider("Loan creation event")    fun exampleEvent(): MessageAndMetadata {        val loanApplication = LoanApplication("1", FraudCheck(false))        val eventString = jacksonObjectMapper().writeValueAsString(loanApplication)        return MessageAndMetadata(eventString.toByteArray(), mapOf("traceId" to "1"))    } }

6. Запустите тест. Теперь он пройдет и выдаст результат, как показано ниже.

Verifying a pact between loan_fulfilment_service and loan_gateway   [Using Directory target/pacts]   Loan creation event     generates a message which       has a matching body (OK)       has matching metadata (OK)

Поздравляем! Теперь вы знаете, как написать контрактный тест для сервисов, взаимодействующих асинхронно. Весь код вы можете найти на github. В следующем блоге мы рассмотрим концепцию Pact broker.

Все коды на изображениях для копирования доступны здесь.


Материал подготовлен в рамках курса «Разработчик на Spring Framework».

Всех желающих приглашаем на двухдневный онлайн-интенсив «Работа с реляционными БД с помощью Spring». На двух занятиях вы узнаете, как работать с БД помощью разных технологий: JDBC, JPA + Hiberante, а также разберемся, какую помощь предлагают в этом различные проекты Spring — Spring JDBC, Spring ORM, Spring Data JPA и Spring Data JDBC

→ РЕГИСТРАЦИЯ


ссылка на оригинал статьи https://habr.com/ru/articles/573320/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *