Ich habe beschlossen, "RestController" zu testen, aber da es schwierig ist, die Anwendung bei jedem Test bereitzustellen, habe ich mich entschlossen, "Mock" zu testen. Wenn es darum geht, mit "Mock" eine mit "Spring" entwickelte Anwendung zu testen, gibt es verschiedene Methoden und Bibliotheken, aber dieses Mal werden wir "MockMVC" verwenden.
Als eine der Funktionen von "Spring Test" können Sie das Verhalten von "Spring MVC" reproduzieren, ohne die Anwendung auf dem Server bereitzustellen. Es gibt zwei Möglichkeiten, MockMVC
zu verwenden: Die eine besteht darin, den DI-Container für Webanwendungen ( WebApplicationContext
) zu verwenden, und die andere darin, den durch Spring Test
generierten DI-Container zu verwenden. Dieses Mal verwenden wir den ersteren: grinsen :.
Einzelheiten entnehmen Sie bitte den TERASOLUNA-Richtlinien. Verwendung der für Unit-Tests verwendeten OSS-Bibliothek - Was ist MockMvc-
Nehmen wir als Beispiel die Todo-Anwendung von TERASOLUNA (REST-Edition). Die zu testende API ist "Post Todos", die Todo erstellt. Wenn Sie "Title" auf das Objekt "TodoResource" und "post" im JSON-Format setzen, werden die ID und das Datum angegeben und die unvollständige "TodoResource" zurückgegeben.
Pakete, Importe, Getter & Setter werden weggelassen. Der zu testende Code ist in diesem Artikel nicht wichtig, daher werde ich Dinge wie "Todo.java" und "TodoService.java" weglassen.
TodoRestController.java
@RestController
@RequestMapping("todos")
public class TodoRestController {
@Inject
TodoService todoService;
@Inject
Mapper beanMapper;
@RequestMapping(method = RequestMethod.POST)
@ResponseStatus(HttpStatus.CREATED)
public TodoResource postTodos(@RequestBody @Validated TodoResource todoResource) {
Todo createdTodo = todoService.create(beanMapper.map(todoResource, Todo.class));
TodoResource createdTodoResponse = beanMapper.map(createdTodo, TodoResource.class);
return createdTodoResponse;
}
}
TodoResource.java
public class TodoResource implements Serializable {
private static final long serialVersionUID = 1L;
private String todoId;
@NotNull
@Size(min = 1, max = 30)
private String todoTitle;
private boolean finished;
private Date createdAt;
}
TodoServiceImpl.java
@Service
@Transactional
public class TodoServiceImpl implements TodoService {
private static final long MAX_UNFINISHED_COUNT = 5;
@Inject
TodoRepository todoRepository;
@Override
public Todo create(Todo todo) {
long unfinishedCount = todoRepository.countByFinished(false);
if (unfinishedCount >= MAX_UNFINISHED_COUNT) {
ResultMessages messages = ResultMessages.error();
messages.add("E001", MAX_UNFINISHED_COUNT);
throw new BusinessException(messages);
}
String todoId = UUID.randomUUID().toString();
Date createdAt = new Date();
todo.setTodoId(todoId);
todo.setCreatedAt(createdAt);
todo.setFinished(false);
todoRepository.create(todo);
return todo;
}
}
web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0">
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<context-param>
<param-name>contextConfigLocation</param-name>
<!-- Root ApplicationContext -->
<param-value>
classpath*:META-INF/spring/applicationContext.xml
classpath*:META-INF/spring/spring-security.xml
</param-value>
</context-param>
<!--Die Definition verschiedener Filter entfällt-->
<servlet>
<servlet-name>restApiServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<!-- ApplicationContext for Spring MVC (REST) -->
<param-value>classpath*:META-INF/spring/spring-mvc-rest.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>restApiServlet</servlet-name>
<url-pattern>/api/v1/*</url-pattern>
</servlet-mapping>
Bestätigen Sie, dass das generierte "TodoResource" -Objekt zurückgegeben wird.
TodoRestControllerTest.java
@RunWith(SpringRunner.class)
@ContextHierarchy({@ContextConfiguration({"classpath:META-INF/spring/applicationContext.xml"}),
@ContextConfiguration({"classpath:META-INF/spring/spring-mvc-rest.xml"})})
@WebAppConfiguration
public class TodoRestControllerTest {
@Autowired
WebApplicationContext webApplicationContext;
MockMvc mockMvc;
ObjectMapper mapper;
@Before
public void setup() {
mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).alwaysDo(log()).build();
mapper = new ObjectMapper();
}
@Test
public void postTodoTest() throws Exception {
String title = "title";
TodoResource todoRequest = new TodoResource();
todoRequest.setTodoTitle(title);
MvcResult result =
mockMvc
.perform(MockMvcRequestBuilders.post("/api/v1/todos")
.content(mapper.writeValueAsString(todoRequest))
.contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON))
.andExpect(status().isCreated()).andReturn();
TodoResource todoResponce =
mapper.readValue(result.getResponse().getContentAsString(), TodoResource.class);
assertThat(todoResponce.getTodoId(), notNullValue());
assertThat(todoResponce.getTodoTitle(), equalTo(title));
assertThat(todoResponce.isFinished(), equalTo(false));
assertThat(todoResponce.getCreatedAt(), notNullValue());
}
}
Wenn ich es ausführe, ist der Statuscode OK (200) anstelle von Erstellt (201) ...: müde:
java.lang.AssertionError: Status expected:<201> but was:<200>
at org.springframework.test.util.AssertionErrors.fail(AssertionErrors.java:54)
at org.springframework.test.util.AssertionErrors.assertEquals(AssertionErrors.java:81)
at org.springframework.test.web.servlet.result.StatusResultMatchers$10.match(StatusResultMatchers.java:665)
at org.springframework.test.web.servlet.MockMvc$1.andExpect(MockMvc.java:171)
at todo.api.TodoRestControllerTest.postTodoTest(TodoRestControllerTest.java:55)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:75)
at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:86)
at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:84)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:252)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:94)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:191)
at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:86)
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:538)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:760)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:460)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:206)
Übrigens, wenn Sie tatsächlich auf dem Server bereitstellen und "Postman" verwenden, um "POST" an "/ api / v1 / todos" zu senden, wird "Todo" erstellt und 201 zurückgegeben.
Danach habe ich sorgfältig geprüft, ob es keine Fehler in der "URL" und den Einstellungen gab, sondern nur die verstrichene Zeit ...
Lesen Sie Verwendung der für Unit-Tests verwendeten OSS-Bibliothek - Was ist MockMvc- Wie ich bemerkt habe, scheint MockMVC
TestDispatcherServlet` zu verwenden, um eine Pseudoanforderung zu erfüllen.
Schauen wir uns hier tatsächlich TestDispatcherServlet.java an.
TestDispatcherServlet.java-4.3.x-
TestDispatcherServlet.java
final class TestDispatcherServlet extends DispatcherServlet {
private static final String KEY = TestDispatcherServlet.class.getName() + ".interceptor";
/**
* Create a new instance with the given web application context.
*/
public TestDispatcherServlet(WebApplicationContext webApplicationContext) {
super(webApplicationContext);
}
Sie rufen den Konstruktor der übergeordneten Klasse "DispatcherServlet" im Konstruktor auf.
Schauen wir uns als nächstes DispatcherServlet.java
an.
DispatcherServlet.java
public class DispatcherServlet extends FrameworkServlet {
/**
* Create a new {@code DispatcherServlet} with the given web application context. This
* constructor is useful in Servlet 3.0+ environments where instance-based registration
* of servlets is possible through the {@link ServletContext#addServlet} API.
* <p>Using this constructor indicates that the following properties / init-params
* will be ignored:
* ~Abkürzung~
*/
public DispatcherServlet(WebApplicationContext webApplicationContext) {
super(webApplicationContext);
setDispatchOptionsRequest(true);
}
Es heißt "Erstellen Sie ein" DispatcherServlet "im angegebenen Anwendungskontext." Schauen wir uns auch den Konstruktor ohne Argumente über diesem Code an
DispatcherServlet.java
public class DispatcherServlet extends FrameworkServlet {
/**
* Create a new {@code DispatcherServlet} that will create its own internal web
* application context based on defaults and values provided through servlet
* init-params. Typically used in Servlet 2.5 or earlier environments, where the only
* option for servlet registration is through {@code web.xml} which requires the use
* of a no-arg constructor.
* ~Abkürzung~
*/
public DispatcherServlet(WebApplicationContext webApplicationContext) {
super(webApplicationContext);
setDispatchOptionsRequest(true);
}
Neben "Erstellen Sie ein neues" DispatcherServlet ", das Ihren eigenen internen Webanwendungskontext bereitstellt.", Dann "Die einzige Möglichkeit, ein Servlet zu registrieren, ist" web.xml ", für die ein Konstruktor ohne Argumente erforderlich ist. "(Freie Übersetzung). Mit anderen Worten, das von MockMVC verwendete TestDispatcherServlet ruft den Konstruktor mit dem Argument DispatcherServlet auf, sodass der Beschreibungsinhalt von web.xml nicht wiedergegeben wird. Bedeutet das? Nach Wörtern suchen, die für die Testversion relevant zu sein scheinen, ist die Version von "Spring Framework" 3.2, aber einige Leute fragten, wie man "web.xml" in einem Test mit "MockMvc" lädt. ..
Does the new Spring MVC Test Framework released in Spring 3.2 test the web.xml configuration?
Die Befragten sagen, dass "Spring-mvc-test die Datei web.xml nicht liest".
Wenn die in "web.xml" beschriebenen Inhalte nicht wiedergegeben werden, bedeutet dies, dass "Servlet-Mapping" nicht funktioniert, sodass der tatsächliche Pfad "/ todos" anstelle von "/ api / v1 / todos" lautet. .. Korrigieren wir also den Testcode und versuchen es erneut.
TodoRestControllerTest.java
MvcResult result = mockMvc
.perform(
MockMvcRequestBuilders.post("/todos").content(mapper.writeValueAsString(todoRequest))
.contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON))
.andExpect(status().isCreated()).andReturn();
Ich habs gemacht!
Es war gut, dass es mit ↑ funktioniert hat, aber es fühlt sich etwas unangenehm an, dass der tatsächliche Anwendungspfad "/ api / v1 / todos" und der Testpfad "/ todos" ist.
Ich weiß, dass Filter mit addFilter
wie folgt hinzugefügt werden können: sos :.
TodoRestControllerTest.java
mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext)
.addFilter(new XTrackMDCPutFilter(), "/**").alwaysDo(log()).build();
Der in diesem Artikel beschriebene Code stammt aus dem Beispiel, aber in der tatsächlichen Entwicklung sollte die URL-Spezifikation korrekt sein, aber es wurde 404, und ich habe viel Zeit damit verbracht, die Ursache zu untersuchen, also werde ich das Ergebnis geben. Ich habe es zusammengefasst. Als ich es mit der REST-Edition der Todo-Anwendung versuchte, wurde 200 zurückgegeben, aber wahrscheinlich funktionierte die unerwartete Einstellung (entsprechend).
Ich konnte bestätigen, dass das erwartete Verhalten erreicht wurde, indem der in web.xml
beschriebene Filter auf MockMVC
angewendet wurde, jedoch" Bei Verwendung von MockMVC
, web.xml
Ich war in Schwierigkeiten, weil ich das Dokument nicht finden konnte, obwohl es einen Kommentar gab, der besagte "Ich kann es nicht lesen" ...! Offizielles Dokument 15.6 Spring MVC Test Framework, Wenn Sie lesen
The Spring MVC Test framework provides first class support for testing Spring MVC code using a fluent API that can be used with JUnit, TestNG, or any other testing framework. It’s built on the Servlet API mock objects from the spring-test module and hence does not use a running Servlet container.
Es heißt, dass es auf dem Servlet-Container nicht funktioniert, daher scheint "web.xml" nichts mit "MockMVC" oder "Spring MVC Test" zu tun zu haben.
Recommended Posts