Ich bin neu im Komponententest. Ich versuche, Tests zu einem Spring Boot Application Controller zu machen. Aber der Test kann mein Modellattribut oder etwas ähnliches nicht finden. Unten findest du meinen Code und hilf mir hoffentlich herauszufinden, was ich falsch mache. Danke im Voraus!Testen des Modellattributs in Spring Boot Controllern
Failure Stack-Trace:
> java.lang.AssertionError: Model attribute 'restaurants'
Expected: a collection with size <2>
but: collection size was <0>
at org.hamcrest.MatcherAssert.assertThat(MatcherAssert.java:20)
at org.springframework.test.web.servlet.result.ModelResultMatchers$1.match(ModelResultMatchers.java:58)
at org.springframework.test.web.servlet.MockMvc$1.andExpect(MockMvc.java:171)
at com.matmr.restaurantpoll.controller.RestaurantControllerTest.should_search(RestaurantControllerTest.java:79)
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:497)
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:459)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:675)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:382)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:192)
RestaurantControllerTest.class
package com.matmr.restaurantpoll.controller;
import static org.hamcrest.Matchers.allOf;
import static org.hamcrest.Matchers.hasItem;
import static org.hamcrest.Matchers.hasProperty;
import static org.hamcrest.Matchers.is;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.model;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.view;
import static org.hamcrest.Matchers.*;
import static org.mockito.Mockito.*;
import java.util.Arrays;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import com.matmr.restaurantpoll.model.Category;
import com.matmr.restaurantpoll.model.Restaurant;
import com.matmr.restaurantpoll.model.filter.RestaurantFilter;
import com.matmr.restaurantpoll.service.RestaurantService;
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest
@WebAppConfiguration
public class RestaurantControllerTest {
@Mock
private RestaurantService restaurantService;
@InjectMocks
private RestaurantController restaurantController;
private MockMvc mockMvc;
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
this.mockMvc = MockMvcBuilders.standaloneSetup(restaurantController).setRemoveSemicolonContent(false).build();
}
@Test
public void should_search() throws Exception {
RestaurantFilter filter = new RestaurantFilter();
filter.setName(null);
Restaurant first = new RestaurantBuilder()
.id(1L)
.name("Abra")
.description("lots of food")
.category(Category.ITALIAN).build();
Restaurant second = new RestaurantBuilder()
.id(2L)
.name("Kadabra")
.description("food for days")
.category(Category.PIZZA).build();
when(restaurantService.findByNameIgnoreCaseContaining(filter)).thenReturn(Arrays.asList(first, second));
this.mockMvc.perform(get("/restaurants"))
.andExpect(status().isOk())
.andExpect(view().name("restaurantList"))
.andExpect(model().attribute("restaurants", hasSize(2)))
.andExpect(model().attribute("restaurants",
hasItem(allOf(
hasProperty("id", is(1L)),
hasProperty("name", is("Abra")),
hasProperty("description", is("lots of food"))
))))
.andExpect(model().attribute("restaurants",
hasItem(allOf(
hasProperty("id", is(2L)),
hasProperty("name", is("Kadabra")),
hasProperty("description", is("food for days"))
))));
verify(restaurantService, times(1)).findByNameIgnoreCaseContaining(filter);
verifyNoMoreInteractions(restaurantService);
}
}
RestaurantController.class
package com.matmr.restaurantpoll.controller;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
import com.matmr.restaurantpoll.model.Restaurant;
import com.matmr.restaurantpoll.model.filter.RestaurantFilter;
import com.matmr.restaurantpoll.service.RestaurantService;
@Controller
@RequestMapping("/restaurants")
public class RestaurantController {
@Autowired
private RestaurantService restaurantService;
@RequestMapping
public ModelAndView pesquisar(@ModelAttribute("filtro") RestaurantFilter filter) {
List<Restaurant> filterRestaurants = restaurantService.findByNameIgnoreCaseContaining(filter);
ModelAndView mv = new ModelAndView("restaurantList");
mv.addObject("restaurants", filterRestaurants);
return mv;
}
}
RestaurantList.html
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://ultraq.net.nz/thymeleaf/layout"
layout:decorator="layout">
<head>
<title>Pesquisa de Restaurantes</title>
</head>
<section layout:fragment="conteudo">
<div layout:include="MensagemGeral"></div>
<div class="panel panel-default">
<div class="panel-heading">
<div class="clearfix">
<h1 class="panel-title liberty-title-panel">Pesquisa de
Restaurantes</h1>
<a class="btn btn-link liberty-link-panel"
th:href="@{/titulos/novo}">Cadastrar Novo Restaurante</a>
</div>
</div>
<div class="panel-body">
<div class="table-responsive">
<table class="table table-bordered table-striped">
<thead>
<tr>
<th class="text-center col-md-1">#</th>
<th class="text-left col-md-2">Nome</th>
<th class="text-left col-md-3">Descrição</th>
<th class="text-left col-md-2">Categoria</th>
<th class="col-md-1"></th>
</tr>
</thead>
<tbody>
<tr th:each="restaurant : ${restaurants}">
<td class="text-center" th:text="${restaurant.id}"></td>
<td class="text-center" th:text="${restaurant.name}"></td>
<td th:text="${restaurant.description}"></td>
<td th:text="${restaurant.category.description}"></td>
<td class="text-center"><a class="btn btn-link btn-xs"
th:href="@{/restaurants/{id}(id=${restaurant.id})}"
title="Editar" rel="tooltip" data-placement="top"> <span
class="glyphicon glyphicon-pencil"></span>
</a> <a class="btn btn-link btn-xs" data-toggle="modal"
data-target="#confirmRemove"
th:attr="data-id=${restaurant.id}, data-name=${restaurant.name}"
title="Excluir" rel="tooltip" data-placement="top"> <span
class="glyphicon glyphicon-remove"></span>
</a></td>
</tr>
<tr>
<td colspan="6" th:if="${#lists.isEmpty(restaurants)}">Nenhum
restaurante foi encontrado!</td>
</tr>
</tbody>
</table>
</div>
</div>
<div layout:include="confirmRemove"></div>
</div>
</section>
</html>
Vielen Dank! Das Hinzufügen von Hashcode und Equals hat den Job erledigt. Über das Entfernen von Anmerkungen, sollte ich alle über dem Klassennamen entfernen? –
Mein Vergnügen. Ja, entferne alle drei. –