Artikel ini akan fokus pada integrasi aplikasi web yang ditulis menggunakan Spring dan bekerja melalui HTTP. Nama Spring Cloud Contract, menurut saya, menyesatkan, karena tidak ada hubungannya dengan cloud.
Ini tentang kontrak API.
Untuk pengujian unit pengendali, mockMCV atau RestAssured sering digunakan. Untuk moka di sisi front-end, server mosk, seperti Wiremock atau Pact, digunakan. Tetapi seringkali, unit test ditulis oleh beberapa orang dan lainnya oleh moki.
Ini dapat menyebabkan masalah dengan integrasi.
Misalnya, server tanpa data dapat mengembalikan 204 NO_CONTENT, dan klien dapat mengharapkan 200 OK dan kosongkan json.
Tidak masalah yang mana dari mereka yang benar. Masalahnya adalah bahwa seseorang membuat kesalahan dan itu tidak akan ditemukan sebelum tahap integrasi.
Masalah ini dirancang untuk diselesaikan oleh kontrak cloud spring.
Apakah kontrak cloud spring
Ini adalah file di mana dialek yaml atau groovyDSL menjelaskan bagaimana tampilan permintaan dan respons. Secara default, semua kontrak ada di folder /src/test/resources/contracts/*
.
Sebagai contoh, uji GET-endpoint paling sederhana
@GetMapping("/bets/{userId}") public ResponseEntity<List<Bet>> getBets(@PathVariable("userId") String userId) { List<Bet> bets = service.getByUserId(userId); if (bets.isEmpty()) { return ResponseEntity.noContent().build(); } return ResponseEntity.ok(bets); }
Kami akan menjelaskan kontraknya
org.springframework.cloud.contract.spec.Contract.make { request { method 'GET' urlPath '/bets/2' } response { status 200 headers { header('Content-Type', 'application/json') } body(''' { "sport":"football", "amount": 1 } ''' ) } }
Selanjutnya, unit test dan json's untuk wiremock dihasilkan dari file ini menggunakan plugin maven atau gradle.
Deskripsi JSON dari mock untuk contoh di atas akan terlihat seperti ini:
{ "id" : "df8f7b73-c242-4664-add3-7214ac6356ff", "request" : { "urlPath" : "/bets/2", "method" : "GET" }, "response" : { "status" : 200, "body" : "{\"amount\":1,\"sport\":\"football\"}", "headers" : { "Content-Type" : "application/json" }, "transformers" : [ "response-template" ] }, "uuid" : "df8f7b73-c242-4664-add3-7214ac6356ff" }
Wiremock dapat dijalankan secara lokal, Anda hanya perlu mengunduh toples dari sini . Secara default, json mokee harus meletakkan folder mappings
.
$java -jar wiremock-standalone-2.18.0.jar
Yang ditunjukkan di bawah ini adalah tes yang dihasilkan. Pustaka RestAssured digunakan secara default, tetapi mockMVC atau spockframework dapat digunakan.
public class UserControllerTest extends ContractBae { @Test public void validate_get_200() throws Exception {
Perlu dicatat bahwa semua kelas yang dihasilkan mewarisi beberapa kelas dasar (mungkin ada beberapa kelas dasar), di mana semua parameter yang diperlukan untuk tes diinisialisasi. Jalur ke kelas dasar dijelaskan dalam pengaturan plugin.
Untuk contoh ini, kelas dasar mungkin terlihat seperti ini:
@RunWith(SpringRunner.class) @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = SccDemoApplication.class) public abstract class ContractBae { @LocalServerPort int port; @Autowired protected WebApplicationContext webApplicationContext; @Before public void configureRestAssured() { RestAssured.port = port; MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext) .build(); RestAssuredMockMvc.mockMvc(mockMvc); } }
Hasilnya, kami mendapatkan bahwa kedua tes dan mokeys diperoleh dari satu sumber. Jika unit test lulus dan ujung depan bersyarat berjalan pada moka, maka tidak akan ada masalah dengan integrasi.
Tapi itu belum semuanya
Moki dapat menggunakan tidak hanya ujung depan, tetapi aplikasi itu sendiri untuk berintegrasi dengan aplikasi lain. Musim semi dapat memulai server mosk, Anda hanya perlu membuat toples dengan mokami dan meneruskan jalurnya ke sana @AutoConfigureStubRunner
annotations
Katakanlah pengontrol kami melakukan HTTP ke aplikasi lain:
@GetMapping("/bets/{userId}") public ResponseEntity<List<Bet>> getBets(@PathVariable("userId") String userId) { if(!isUsetExists(userId)) { return ResponseEntity.notFound().build(); } List<Bet> bets = service.getByUserId(userId); if (bets.isEmpty()) { return ResponseEntity.noContent().build(); } return ResponseEntity.ok(bets); } private boolean isUsetExists(String userId) { try { restTemplate.getForObject("/exists/" + userId, Void.class); return true; } catch (HttpStatusCodeException ignore) { return false; } }
Maka Anda hanya perlu menjelaskan mari jar dengan mokami di kelas dasar
@RunWith(SpringRunner.class) @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = SccDemoApplication.class) @AutoConfigureStubRunner(ids = {"me.dehasi.contracts.demo:sub-service-stubs:+:stubs:8090"}, stubsMode = StubRunnerProperties.StubsMode.LOCAL) public abstract class ContractBase {
Karena ini adalah tes controller, kemudian potongan ascii-doc dapat dihasilkan dari tes yang sama (artikel lengkap tentang rest-docs sudah ada di hub ).
Apa yang kita miliki
Ternyata kami memiliki satu sumber kontrak API, yang dijelaskan dalam bahasa yang dapat dibaca manusia, dan darinya kami membuat unit test (secara teoritis juga dokumentasi), dan dari sana moki. Pendekatan ini mengurangi risiko kesalahan integrasi antara aplikasi web.
Contohnya bisa dilihat di situs web resmi misalnya .
Contoh kode dalam artikel diambil dari proyek paling sederhana di sini .