{"id":781,"date":"2015-11-30T12:48:40","date_gmt":"2015-11-30T10:48:40","guid":{"rendered":"http:\/\/invidit.de\/blog\/?p=781"},"modified":"2015-12-02T11:27:11","modified_gmt":"2015-12-02T09:27:11","slug":"den-rest-der-welt-auch-testen","status":"publish","type":"post","link":"https:\/\/invidit.de\/blog\/den-rest-der-welt-auch-testen\/","title":{"rendered":"Den REST der Welt auch testen"},"content":{"rendered":"<p>Hallo Spa\u00df-Coder.<\/p>\n<p>Bei unseren Artikeln zum Thema <a href=\"http:\/\/invidit.de\/blog\/tag\/testautomatisierung\/\" target=\"_blank\">Testautomatisierung<\/a> haben wir immer mal wieder darauf hingewiesen, dass wir keine Abh\u00e4ngigkeiten zur Peripherie haben m\u00f6chten, um m\u00f6glichst ungest\u00f6rt und schnell testen zu k\u00f6nnen. Wer hat sich bei diesen Artikeln nicht schon einmal die Frage gestellt, was das denn in der Praxis bedeutet?<\/p>\n<p>Heute m\u00f6chten wir uns an einem ganz konkreten Beispiel einmal anschauen, wie das nun funktioniert mit dem Abtrennen der Abh\u00e4ngigkeiten f\u00fcr den Test.<\/p>\n<p>&nbsp;<\/p>\n<h1>Devided REST-Rooms<\/h1>\n<p>Das Konzept der RESTful Services teilt sich in zwei Teile auf.<\/p>\n<p>Auf der einen Seite haben wir den <strong>Server<\/strong>, der REST-Anfragen entgegen nimmt, verarbeitet und ein Ergebnis zur\u00fcckliefert. Die Kommunikation erfolgt via HTTP, das Austauschformat ist frei w\u00e4hlbar, hier hat sich allerdings f\u00fcr die meisten Services JSON als Format etabliert, wenn es um den Austausch von Daten geht.<\/p>\n<p>Auf der anderen Seite steht der <strong>Client<\/strong>, der die REST-Anfrage via HTTP\u00a0 stellt, das Ergebnis vom REST-Server bekommt und weiterverarbeitet.<\/p>\n<p>Zwischen diesen beiden beteiligten findet eine Kommunikation statt. Der eine kann ohne den anderen nicht funktionieren, es wird immer eine Gegenstelle ben\u00f6tigt, um die Verarbeitung vollst\u00e4ndig abzuschlie\u00dfen. Genau dies f\u00fchrt zu dem Problem, dass wir die beiden Teile des REST-Services nicht gut testen k\u00f6nnen.<\/p>\n<p>Was aber k\u00f6nnen wir tun? Zun\u00e4chst sichern wir alle Einzelteile auf der jeweiligen Seite die wir implementieren mit Unit-Tests ab. Das machen wir immer, also auf jeden Fall auch hier. Je fr\u00fcher wir in der Kette einen Fehler finden &#8211; und die Unit ist die Kleinste Einheit, die einen Fehler haben kann &#8211; desto leichter f\u00e4llt die Analyse, die Behebung und das Sicherstellen, dass der Fehler nicht wieder auftritt.<\/p>\n<p>&nbsp;<\/p>\n<h1>REST in Peace &#8211; Client<\/h1>\n<p>Wir haben also unseren RESTful-Client geschrieben und die interne Verarbeitungen mit Unit-Tests abgesichert. Nun m\u00f6chten wir aber auch die Methoden, welche die REST-Daten ben\u00f6tigen automatisiert testen. Wie machen wir das?<\/p>\n<p>&nbsp;<\/p>\n<h2>Mocking<\/h2>\n<p>Auf dieses Konzept sind wir in der Vergangenheit bereits eingegangen. Wir nutzen ein Mocking-Framework um die externen Abh\u00e4ngigkeiten zu verstecken. Wir nutzen in solchen F\u00e4llen f\u00fcr unseren Java-Code das Framework <a href=\"http:\/\/mockito.org\/\" target=\"_blank\">Mockito<\/a>, ein aktives Open-Source-Projekt, welches sehr m\u00e4chtig und gut dokumentiert ist.<\/p>\n<p>Nehmen wir als Beispiel eine Kochbuch-Anwendung. Hierbei werden Rezepte (Recipes) und Zutaten (Ingredients) verwaltet. Wir wollen das Repository, welches die Rezepte vom Server abfragt testen, ohne dass die tats\u00e4chliche Abfrage zum Server durchgef\u00fchrt wird.<\/p>\n<pre class=\"lang:default decode:true \">@Test\r\npublic void testFindRecipesByIngredientName() throws Exception {\r\n\tJsonConfiguration jsonConfiguration = mock(jsonConfiguration.class)\r\n    RestService restService = mock(RestService.class);\r\n\r\n    Recipe[] expected = new Recipe[1];\r\n    Recipe recipe = new Recipe();\r\n    recipe.setId(\"38\");\r\n    recipe.setName(\"Spaghetti Bolognese\");\r\n\texpected[0] = recipe;\r\n\r\n    when(jsonConfiguration.getUrl()).thenReturn(\"\");\r\n    when(restService.findObject(anyString(), anyString(), any())).thenReturn(expected);\r\n    \r\n\tRecipeRestRepository sut =\r\n            new RecipeRepository(jsonConfiguration, restService);\r\n\tRecipe[] actual = sut.ReadByIngredientName(\"Spaghetti\");\r\n    \r\n\tAssertions.assertThat(actual[0].getId()).isEqualTo(\"38\");\r\n    Assertions.assertThat(actual[0].getName()).isEqualTo(\"Spaghetti Bolognese\");\r\n}<\/pre>\n<p>&nbsp;<\/p>\n<p>Wie wir sehen hilft es uns hier, dass wir mit Constructor-based Injection arbeiten. Auf diese Weise k\u00f6nnen wir dem REST-Client im Konstruktor die erstellten Mock-Objekte \u00fcbergeben und diesen damit vom Server abschneiden. Wir k\u00f6nnen also in Frieden unseren Test durchf\u00fchren.<\/p>\n<p>Durch das Mocken der Objekte befinden wir uns weiterhin auf der Ebene des Unit-Tests, da wir die integrierten Abh\u00e4ngigkeiten durch Mock-Objekte ersetzt haben.<\/p>\n<p>&nbsp;<\/p>\n<h2>Stub-Server<\/h2>\n<p>Wollen wir nun aber tats\u00e4chlich mal schauen, wie sich der REST-Client denn in einer &#8222;echten Welt&#8220; verh\u00e4lt, m\u00fcssen wir einen Integrationstest schreiben. Wir brauchen dazu auf jeden Fall einen REST-Server, der unsere REST-Anfrage beantwortet.<\/p>\n<pre class=\"lang:java decode:true\">[configuration.server=http:\/\/testserver:8082]\r\n\r\n@Override\r\npublic Recipe[] findByIngredient(String ingredientName) {\r\n\tfinal String url = jsonConfiguration.getUrl() + \"\/findByIngredient\/\" + ingredientName;\r\n\treturn restService.findObject(url, \"Error finding recipe.\", Recipe[].class);\r\n}<\/pre>\n<p>Vielleicht stellt ihr euch die Frage, warum wir denn nicht gleich gegen den echten Server testen. Nun, das k\u00f6nnen wir in dem Beispiel nat\u00fcrlich gefahrlos tun, hier werden ja nur GET-Anfragen aufgerufen, also Daten abgefragt.<\/p>\n<p>Aber steht uns der Server \u00fcberhaupt zur Verf\u00fcgung? Wenn wir ein Fremdes System anbinden, ist das ggf. gar nicht der Fall. Daf\u00fcr m\u00fcssen wir unter Umst\u00e4nden sehr viel Aufwand betreiben um die Infrastruktur entsprechend einzurichten.<\/p>\n<p>Auf der Anderen Seite wollen wir ja auch alle Facetten des REST-Clients testen. Wie sieht es z.B. aus, wenn wir keinen GET, sondern einen PUT machen, also Daten ver\u00e4ndern. Oder gar ein DELETE, also Daten l\u00f6schen. Das m\u00f6chten wir sicher nicht auf einem Produktivsystem machen.<\/p>\n<p>Den Stub-Server k\u00f6nnen wir f\u00fcr alle Aufrufe so gestalten, dass er genau die Daten zur\u00fcckgibt, die wir erwarten, ohne teure Anbindung an weitere Infrastrukturen. Auf diesem Weg k\u00f6nnen wir auch das Fehlerverhalten testen, indem wir einfach mal &#8222;M\u00fcll&#8220; oder Fehlercodes &#8211; wie etwa keine Berechtigung oder einen internen Serverfehler &#8211; zur\u00fcckgeben.<\/p>\n<p>&nbsp;<\/p>\n<h1>Control the REST &#8211; Server<\/h1>\n<p>Auf Seiten des REST-Servers haben wir eine ganz andere Herausforderung. In aller Regel haben wir eine Webanwendung mit einem Controller und einem RequestMapping. Wie aber testen wir dies? Das Mapping, den Controller, die R\u00fcckgabe, das Fehlerverhalten?<\/p>\n<p><strong>Hinweis:<\/strong> Dieses Verfahren funktioniert nur dann, wenn wir unseren Controller mit dem Framework<a href=\"http:\/\/docs.spring.io\/spring\/docs\/current\/spring-framework-reference\/html\/mvc.html\" target=\"_blank\"> Spring Web MVC <\/a>implementiert haben, da die eingesetzte Mocking-Bibliothek ebenfalls aus dem Springframework stammt (<a href=\"http:\/\/docs.spring.io\/spring\/docs\/current\/spring-framework-reference\/html\/integration-testing.html\" target=\"_blank\">Spring Test<\/a>).<\/p>\n<p>Auch hier testen wir zun\u00e4chst die kleinsten Einheiten mit Hilfe von Unit Tests. F\u00fcr die request-basierten Tests nutzen wir wiederum die M\u00f6glichkeit des Mockings. Spring Test bietet uns hier die M\u00f6glichkeit komfortabel einen Controller \u00fcber einen gefakten Request aufzurufen und das Ergebnis zu pr\u00fcfen.<\/p>\n<pre class=\"lang:java decode:true\">@Test\r\npublic void testGetRecipeByIngredientRespondsWithJson() throws Exception {\r\n\tRecipeRepository recipeRepository = mock(RecipeRepository.class);\r\n\r\n\tString recipeId = \"38\";\r\n\tString recipeName = \"Spaghetti Bolognese\";\r\n\r\n\tRecipe recipe = new Recipe();\r\n    recipe.setId(recipeId);\r\n    recipe.setName(recipeName);\r\n\t\r\n\twhen(recipeRepository.findByIngredient(ingredientName))\r\n\t\t\t.thenReturn(recipe);\r\n\r\n\tfinal MockMvc mockMvc = standaloneSetup(new RecipeController(recipeRepository))\r\n\t\t\t.build();\r\n\r\n\tmockMvc.perform(get(\"\/recipes\/findByIngredientName\/\" + \"Spaghetti\"))\r\n\t\t\t.andExpect(status().isOk())\r\n\t\t\t.andExpect(\r\n\t\t\t\t\tcontent().contentType(MediaType.APPLICATION_JSON_VALUE))\r\n\t\t\t.andExpect(jsonPath(\"$.recipeId\").value(recipeId))\r\n\t\t\t.andExpect(jsonPath(\"$.recipeName\").value(recipeName));\r\n}<\/pre>\n<p>Wie wir sehen k\u00f6nnen wir nach dem Setup des Controllers mit Hilfe des Test-Frameworks von Spring diesen sch\u00f6n einfach aufrufen und somit auf Herz und Nieren Testen.<\/p>\n<p>Dies ist nat\u00fcrlich einer der einfachsten Tests. Spannend wird es auch hier insbesondere dann, wenn wir Fehlerverhalten, falsche Parameter, etc. testen.<\/p>\n<p>&nbsp;<\/p>\n<h1>Zusammenfassung<\/h1>\n<p>Wie so oft kommt es auch beim Test von RESTful Services darauf an, die Stellen zu erkennen, die uns den Test erschweren und diese m\u00f6glichst abzukapseln.<\/p>\n<p>Wichtig ist noch der Hinweis, dass wir beim Test immer darauf achten m\u00fcssen, nicht das wegzumocken, was wir eigentlich testen m\u00f6chten. Das ist leichter gesagt, als es sich in der Realit\u00e4t darstellt. Ist am Anfang noch klar, was getestet werden soll, verrennt man sich ggf. schnell in Mock-Objekten, insbesondere bei Legacy-Code.<\/p>\n<p>Bei neuen REST-Clients oder REST-Services sollte dieses Problem nicht so schnell auftreten, Vorsicht ist trotzdem geboten. Aber die lassen wir ja ohnehin immer walten bei unserem Code, wir sind schlie\u00dflich Profis \ud83d\ude09<\/p>\n<p>&nbsp;<\/p>\n<p>In diesem Sinne, viel Spa\u00df beim Testen der RESTs dieser Welt \ud83d\ude09<\/p>\n<p>Eure Spa\u00df-Coder<\/p>\n<p>&nbsp;<\/p>\n<p>Dieser Artikel basiert neben unseren Erfahrungen auf den Ausf\u00fchrungen aus:<\/p>\n<ul>\n<li id=\"gs_cit0\" class=\"gs_citr\" tabindex=\"0\">Kurzes, englischsprachiges Video zum Thema auf der Seite des <a href=\"http:\/\/www.leveluplunch.com\/java\/tutorials\/030-testing-spring-rest-webservice-controllers\/\" target=\"_blank\">Level Up Lunch<\/a><\/li>\n<\/ul>\n","protected":false},"excerpt":{"rendered":"<p>Hallo Spa\u00df-Coder. Bei unseren Artikeln zum Thema Testautomatisierung haben wir immer mal wieder darauf hingewiesen, dass wir keine Abh\u00e4ngigkeiten zur Peripherie haben m\u00f6chten, um m\u00f6glichst ungest\u00f6rt und schnell testen zu k\u00f6nnen. Wer hat sich bei diesen Artikeln nicht schon einmal die Frage gestellt, was das denn in der Praxis bedeutet? Heute m\u00f6chten wir uns an [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":786,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":[],"categories":[90],"tags":[65,127,100,69,77,75],"_links":{"self":[{"href":"https:\/\/invidit.de\/blog\/wp-json\/wp\/v2\/posts\/781"}],"collection":[{"href":"https:\/\/invidit.de\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/invidit.de\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/invidit.de\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/invidit.de\/blog\/wp-json\/wp\/v2\/comments?post=781"}],"version-history":[{"count":12,"href":"https:\/\/invidit.de\/blog\/wp-json\/wp\/v2\/posts\/781\/revisions"}],"predecessor-version":[{"id":810,"href":"https:\/\/invidit.de\/blog\/wp-json\/wp\/v2\/posts\/781\/revisions\/810"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/invidit.de\/blog\/wp-json\/wp\/v2\/media\/786"}],"wp:attachment":[{"href":"https:\/\/invidit.de\/blog\/wp-json\/wp\/v2\/media?parent=781"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/invidit.de\/blog\/wp-json\/wp\/v2\/categories?post=781"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/invidit.de\/blog\/wp-json\/wp\/v2\/tags?post=781"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}