Споживчі контракти з Pact

1. Огляд

У цій короткій статті ми розглянемо концепцію підписаних споживачами контрактів.

Ми будемо тестувати інтеграцію із зовнішньою службою REST за допомогою контракту, який ми визначаємо за допомогою бібліотеки Pact . Цей контракт може бути визначений клієнтом, потім підібраний постачальником та використаний для розвитку його послуг.

Ми також створимо тести на основі контракту як для клієнтських, так і для провайдерських додатків.

2. Що таке Пакт ?

Використовуючи Pact , ми можемо визначити очікування споживачів щодо даного провайдера (це може бути послуга HTTP REST) ​​у формі договору (звідси і назва бібліотеки).

Ми збираємось укласти цей контракт із використанням DSL, наданого Pact . Після визначення ми можемо перевірити взаємодію між споживачами та постачальником, використовуючи макетну послугу, створену на основі визначеного контракту. Крім того, ми перевіримо послугу на предмет контракту за допомогою фіктивного клієнта.

3. Залежність Мейвена

Для початку нам потрібно додати залежність Maven до бібліотеки pact-jvm-consumer-junit_2.11 :

 au.com.dius pact-jvm-consumer-junit_2.11 3.5.0 test 

4. Визначення контракту

Коли ми хочемо створити тест за допомогою Pact , спочатку нам потрібно визначити @Rule, яке буде використовуватися в нашому тесті:

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

Ми передаємо ім’я постачальника, хост та порт, на якому буде запущено макет сервера (який створений з контракту).

Скажімо, служба визначила контракт на два методи HTTP, які вона може обробити.

Перший метод - це запит GET, який повертає JSON із двома полями. Коли запит вдається, він повертає код відповіді 200 HTTP та заголовок типу C ontent для JSON.

Давайте визначимо такий контракт, використовуючи Pact .

Нам потрібно використовувати анотацію @Pact та передати ім’я споживача, для якого визначено контракт. Усередині анотованого методу ми можемо визначити наш контракт GET:

@Pact(consumer = "test_consumer") public RequestResponsePact createPact(PactDslWithProvider builder) { Map headers = new HashMap(); headers.put("Content-Type", "application/json"); return builder .given("test GET") .uponReceiving("GET REQUEST") .path("/pact") .method("GET") .willRespondWith() .status(200) .headers(headers) .body("{\"condition\": true, \"name\": \"tom\"}") (...) }

Використовуючи DSL Pact, ми визначаємо, що для даного запиту GET ми хочемо повернути відповідь 200 із конкретними заголовками та тілом.

Друга частина нашого контракту - це метод POST. Коли клієнт надсилає запит POST на шлях / пакт із належним тілом JSON, він повертає 201 код відповіді HTTP.

Давайте визначимо такий контракт з Pact:

(...) .given("test POST") .uponReceiving("POST REQUEST") .method("POST") .headers(headers) .body("{\"name\": \"Michael\"}") .path("/pact") .willRespondWith() .status(201) .toPact();

Зверніть увагу, що нам потрібно викликати метод toPact () наприкінці контракту, щоб повернути екземпляр RequestResponsePact .

4.1. Утворений артефакт пакту

За замовчуванням файли Pact генеруються у цільовій папці. Щоб налаштувати цей шлях, ми можемо налаштувати плагін maven-surefire:

 org.apache.maven.plugins maven-surefire-plugin   target/mypacts   ... 

Складання Maven буде генерувати файл з ім'ям test_consumer-test_provider.json в мішені / mypacts папку, яка містить структуру запитів і відповідей:

{ "provider": { "name": "test_provider" }, "consumer": { "name": "test_consumer" }, "interactions": [ { "description": "GET REQUEST", "request": { "method": "GET", "path": "/" }, "response": { "status": 200, "headers": { "Content-Type": "application/json" }, "body": { "condition": true, "name": "tom" } }, "providerStates": [ { "name": "test GET" } ] }, { "description": "POST REQUEST", ... } ], "metadata": { "pact-specification": { "version": "3.0.0" }, "pact-jvm": { "version": "3.5.0" } } }

5. Тестування клієнта та постачальника з використанням контракту

Тепер, коли у нас є наш контракт, ми можемо використовувати його для створення тестів проти нього як для клієнта, так і для постачальника.

У кожному з цих тестів буде використано макет свого аналога, який базується на контракті, тобто:

  • клієнт буде використовувати макет-провайдера
  • постачальник використовуватиме фіктивний клієнт

Фактично, тести проводяться за контрактом.

5.1. Тестування клієнта

Після того, як ми визначили контракт, ми можемо перевірити взаємодію зі службою, яка буде створена на основі цього контракту. Ми можемо створити звичайний тест JUnit, але нам потрібно пам’ятати, що на початку тесту слід помістити анотацію @PactVerification .

Давайте напишемо тест для запиту GET:

@Test @PactVerification() public void givenGet_whenSendRequest_shouldReturn200WithProperHeaderAndBody() { // when ResponseEntity response = new RestTemplate() .getForEntity(mockProvider.getUrl() + "/pact", String.class); // then assertThat(response.getStatusCode().value()).isEqualTo(200); assertThat(response.getHeaders().get("Content-Type").contains("application/json")).isTrue(); assertThat(response.getBody()).contains("condition", "true", "name", "tom"); }

@PactVerification анотацію піклується про запуск служби HTTP. У тесті нам потрібно лише надіслати запит GET і підтвердити, що наша відповідь відповідає контракту.

Додамо також тест для виклику методу POST:

HttpHeaders httpHeaders = new HttpHeaders(); httpHeaders.setContentType(MediaType.APPLICATION_JSON); String jsonBody = "{\"name\": \"Michael\"}"; // when ResponseEntity postResponse = new RestTemplate() .exchange( mockProvider.getUrl() + "/create", HttpMethod.POST, new HttpEntity(jsonBody, httpHeaders), String.class ); //then assertThat(postResponse.getStatusCode().value()).isEqualTo(201);

Як бачимо, код відповіді на запит POST дорівнює 201 - точно так, як це було визначено в контракті Pact .

Оскільки ми використовували анотацію @PactVerification () , бібліотека Pact запускає веб-сервер на основі попередньо визначеного контракту до нашого тестового випадку.

5.2. Тестування постачальника

Другим кроком нашої перевірки контракту є створення тесту для провайдера за допомогою фіктивного клієнта на основі контракту.

Впровадження нашого постачальника буде керуватися цим контрактом у режимі TDD.

Для нашого прикладу ми використаємо API REST Spring Boot.

Спочатку, щоб створити наш тест JUnit, нам потрібно додати залежність pact-jvm-provider-junit_2.11:

 au.com.dius pact-jvm-provider-junit_2.11 3.5.0 test 

Це дозволяє нам створити тест JUnit за допомогою PactRunner та вказати ім'я постачальника та розташування артефакту Pact:

@RunWith(PactRunner.class) @Provider("test_provider") @PactFolder("pacts") public class PactProviderTest { //... }

For this configuration to work, we have to place the test_consumer-test_provider.json file in the pacts folder of our REST service project.

Next, we'll define the target to be used for verifying the interactions in the contract and start up the Spring Boot app before running the tests:

@TestTarget public final Target target = new HttpTarget("http", "localhost", 8082, "/spring-rest"); private static ConfigurableWebApplicationContext application; @BeforeClass public static void start() { application = (ConfigurableWebApplicationContext) SpringApplication.run(MainApplication.class); }

Finally, we'll specify the states in the contract that we want to test:

@State("test GET") public void toGetState() { } @State("test POST") public void toPostState() { }

Running this JUnit class will execute two tests for the two GET and POST requests. Let's take a look at the log:

Verifying a pact between test_consumer and test_provider Given test GET GET REQUEST returns a response which has status code 200 (OK) includes headers "Content-Type" with value "application/json" (OK) has a matching body (OK) Verifying a pact between test_consumer and test_provider Given test POST POST REQUEST returns a response which has status code 201 (OK) has a matching body (OK)

Note that we haven't included the code for creating a REST service here. The full service and test can be found in the GitHub project.

6. Conclusion

In this quick tutorial, we had a look at Consumer Driven Contracts.

Ми створили контракт, використовуючи бібліотеку Pact . Після того, як ми визначили контракт, ми змогли перевірити клієнта та службу на відповідність договору та стверджувати, що вони відповідають специфікації.

Реалізацію всіх цих прикладів та фрагментів коду можна знайти у проекті GitHub - це проект Maven, тому його слід легко імпортувати та запускати як є.