J'ai décidé de tester RestController
, mais comme il est difficile de déployer l'application à chaque fois que je teste, j'ai décidé de tester en utilisant Mock
.
Quand il s'agit d'utiliser Mock
pour tester une application développée avec Spring
, il existe plusieurs méthodes et bibliothèques, mais cette fois nous utiliserons MockMVC
.
L'une des fonctionnalités fournies par Spring Test
, vous pouvez reproduire le comportement de Spring MVC
sans déployer l'application sur le serveur. Il y a deux façons d'utiliser MockMVC
, l'une consiste à utiliser le conteneur DI pour les applications Web ( WebApplicationContext
), et l'autre consiste à utiliser le conteneur DI généré par Spring Test
. Cette fois, nous utiliserons l'ancien: grinning:.
Pour plus de détails, veuillez vous référer aux directives TERA SOLUNA. Comment utiliser la bibliothèque OSS utilisée pour le test unitaire -Qu'est-ce que MockMvc-
Prenons comme exemple l'application Todo de TERASOLUNA (édition REST).
L'API à tester est «post Todos» qui crée Todo.
Si vous définissez Title
sur l'objet TodoResource
et post
au format JSON, l'id et la date seront donnés et le TodoResource
incomplet sera retourné.
Les packages, les importations, les getters et les setters sont omis. Le code testé n'est pas important dans cet article, je vais donc omettre des choses comme
Todo.java
etTodoService.java
.
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>
<!--La définition des différents filtres est omise-->
<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>
Confirmez que l'objet TodoResource
généré est renvoyé.
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());
}
}
Quand je l'exécute, le code d'état est OK (200) au lieu de Créé (201) ...: fatigué:
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)
À propos, si vous déployez réellement sur le serveur et utilisez Postman
pour POST
vers / api / v1 / todos
, Todo
sera créé et 201 sera retourné.
Après cela, j'ai soigneusement vérifié qu'il n'y avait pas d'erreurs dans l'URL et les paramètres, mais seulement le temps passait ...
Lisez Comment utiliser la bibliothèque OSS utilisée pour le test unitaire - Qu'est-ce que MockMvc- Comme je l'ai remarqué, «MockMVC» semble utiliser «TestDispatcherServlet» pour répondre à une pseudo requête.
Regardons en fait TestDispatcherServlet.java
ici.
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);
}
Vous appelez le constructeur de la classe parent DispatcherServlet
dans le constructeur.
Ensuite, jetons un œil à DispatcherServlet.java
.
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:
* ~Abréviation~
*/
public DispatcherServlet(WebApplicationContext webApplicationContext) {
super(webApplicationContext);
setDispatchOptionsRequest(true);
}
Il dit "Créer un" DispatcherServlet "dans le contexte d'application spécifié." Regardons également le constructeur sans argument au-dessus de ce code
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.
* ~Abréviation~
*/
public DispatcherServlet(WebApplicationContext webApplicationContext) {
super(webApplicationContext);
setDispatchOptionsRequest(true);
}
À côté de "Créer un nouveau" Servlet de diffusion "qui fournit votre propre contexte d'application Web interne.", Ensuite "La seule façon d'enregistrer un servlet est" web.xml ", qui nécessite l'utilisation d'un constructeur sans argument. "(Traduction gratuite).
En d'autres termes, le TestDispatcherServlet
utilisé par MockMVC
appelle le constructeur avec l'argument de DispatcherServlet
, donc le contenu de la description de web.xml
n'est pas reflété. Cela signifie t-il?
En recherchant des mots qui semblent pertinents pour l'essai, la version de Spring Framework
est 3.2, mais certaines personnes demandaient comment charger web.xml
dans un test utilisant MockMvc
. ..
Does the new Spring MVC Test Framework released in Spring 3.2 test the web.xml configuration?
Les répondants disent que "Spring-mvc-test ne lit pas le fichier web.xml".
Si le contenu décrit dans web.xml
n'est pas reflété, cela signifie que servlet-mapping
ne fonctionne pas, donc le chemin réel sera / todos
au lieu de / api / v1 / todos
. .. Alors corrigeons le code de test et réessayons.
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();
Je l'ai fait!
C'était bien que cela fonctionne avec ↑, mais cela semble un peu désagréable que le chemin réel de l'application soit / api / v1 / todos
et que le chemin du test soit / todos
.
Je sais que les filtres peuvent être ajoutés avec ʻaddFilter` comme suit: sos:.
TodoRestControllerTest.java
mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext)
.addFilter(new XTrackMDCPutFilter(), "/**").alwaysDo(log()).build();
Le code décrit dans cet article est tiré de l'exemple, mais dans le développement actuel, la spécification de ʻURL` devrait être correcte, mais elle est devenue 404, et j'ai passé beaucoup de temps à rechercher la cause, donc je vais donner le résultat. Je l'ai résumé. Lorsque je l'ai essayé avec l'édition REST de l'application Todo, 200 a été renvoyé, mais le paramètre inattendu fonctionnait probablement (approprié).
En appliquant le filtre etc. décrit dans web.xml
à MockMVC
, il a été confirmé que le comportement attendu était atteint, mais" Lors de l'utilisation de MockMVC
, web.xml
J'avais des ennuis parce que je n'ai pas trouvé le document même s'il y avait un commentaire disant "Je ne peux pas le lire" ...! Document officiel 15.6 Spring MVC Test Framework, Si vous lisez
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.
Il dit que cela ne fonctionne pas sur le conteneur de servlet, donc il semble que web.xml
n'a rien à voir avec MockMVC
ou Spring MVC Test
.
Recommended Posts