Spring Boot Testing
Spring Boot bietet sehr viel Komfort beim Anwendungstest. Man muß nur die richtige Annotation (injezierbare Services aufgeführt)
- @SpringBootTest
@Autowired private TestRestTemplate restTemplate;
- @WebMvcTest
@Autowired private MockMvc mvc;
- @WebFluxTest
- @RestClientTest
@Autowired private MockRestServiceServer server;
- @DataJpaTest
- @JdbcTest
- @DataMongoTest
- ...
und schon kann man sich ganz zentrale Services per @Autowired
injecten als Mocks lassen. Deren Verhalten muß man allerdings häufig im Test über eine Fluent-API konfigurieren (denn man will ja unterschiedliche Szenarien testen).
Integrationtesting
Eine typische Testklasse für eine Webapplikation sieht so aus:
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(
classes = MySpringBootApplication.class, // Startup-Klasse (mit main-Methode)
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@SpringApplicationConfiguration(
classes = MyApplication.class,
locations = { "classpath:META-INF/test-context.xml" })
@ActiveProfiles("test") // load application-test.yml
public class MyApplicationTest {
@Autowired
private MyService service;
@Test
public void testServiceCall() {
service.method();
}
}
@RunWith(SpringJUnit4ClassRunner.class)
Dieser JUnit-Testrunner sollte grundsätzlich bei Spring-basierten Komponenten verwendet werden. Er stellt die Grundvoraussetzung dar - sonst darf man sich nicht wundern, wenn spring-spezifische Aspekte (z. B. der Aufbau des ApplicationContext aus xml-Dateien @ContextConfiguration(locations = { "classpath:mymodule-context.xml" })
) nicht funktionieren.
@SpringApplicationConfiguration
Diese Annotation wird eingesetzt, um die zu testenden Applikation zu initialisieren. Deshalb darf die SpringBoot-Main-Class nicht fehlen. Zudem werden evtl. noch ein paar Initializer für die Testumgebung aufgeführt (z. B. zur Initialisierung von Mocks).
Hier kann man auch Spring-Initialisierung über Spring-Kontexte in XML-Form anstoßen:
@SpringApplicationConfiguration(
classes = MySpringBootApplication.class,
locations = { "classpath:test-context.xml" })
@IntegrationTest
@WebIntegrationTest
Handelt es sich um einen @IntegrationTest
, der auch eine @WebAppConfiguration
benötigt, dann sollte man vermutlich @WebIntegrationTest
verwenden. In einem WebIntegrationTest wird ein Applicationserver (Servlet-Container - per Default Toncat) gestartet
Applikationskonfiguration
Die Applikationskonfiguration wird aus der application.properties
gezogen, kann aber über
@WebIntegrationTest({"server.port=0", "management.port=0"})
für jeden Test übersteuert werden.
Freien Port suchen
Es macht Sinn, einen freien (!!!) Port für zu startenden Applicationserver zu suchen:
@WebIntegrationTest({"server.port=0", "management.port=0"})
und diesen Port dann per
@Value("${local.server.port}")
int port;
zu injezieren.
Rest-Tests
Das Testen von Rest-Schnittstellen läuft über TestRestTemplate
.
Vorsicht vor Test-Fakes
Da die Rest-Services ganz normale Java-Beans sind, läuft man Gefahr die Beans über Java-Aufrufe zu testen. Hierbei wird allerdings nicht die Funktionalität getestet, die ein RICHTIGER Applikationsclient verwendet. Beispielsweise wird das Marshalling/Unmarshalling nicht durchlaufen.
Diese Abkürzungen fangen am Anfang vielleicht gar nicht auf, doch spätestens, wenn beispielsweise auf HttpServletRequest
im Rest-Service zugegriffen werden soll ... eine solche Instanz dann aber gar nicht existiert.
Option 1: TestRestTemplate
Basic-Authentication:
new TestRestTemplate("username", "password")
MessageConverters:
restTemplate = new RestTemplate();
List<HttpMessageConverter<?>> list = new ArrayList<HttpMessageConverter<?>>();
list.add(new MappingJacksonHttpMessageConverter());
restTemplate.setMessageConverters(list);
RestTemplate: postForObject vs. postForEntity
Entity liefert nicht nur das eigentliche Result-Objekt des Webservice, sondern auch gleichzeitig Status-Code, ...
Empfehlung: verwende postForEntity
Beispiel:
RestTemplate restTemplate = new RestTemplate();
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
String requestInJsonFormat =
"{\"question\":\"What is your name?\"}";
HttpEntity<String> requestEntity =
new HttpEntity<String>(requestInJsonFormat, headers);
String answer =
restTemplate.postForObject(
"http://localhost/orakel",
requestEntity,
String.class);
Option 2: MockMvc
Option 3...n:
Irgendweinen anderen Http-Client verwenden.