2

I'm new to unit testing. I'm trying to make tests to a Spring Boot application controller. But the test cannot find my model attribute or something like that. Below you can find my code and hopefully help me discover what I am doing wrong. Thanks in advance!

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>
Miloš Milivojević
  • 5,219
  • 3
  • 26
  • 39
Mateus M.R.
  • 86
  • 1
  • 9

1 Answers1

1

First off, you should remove the annotations from your test class, they are used for integration testing and will only slow your test down.

As far as your problem goes, my guess is that your RestaurantFilter does not implement equals and hashCode so when you use it in

when(restaurantService.findByNameIgnoreCaseContaining(filter))
    .thenReturn(Arrays.asList(first, second));

Mockito actually doesn't match the argument with the mock so it doesn't return the array you've given it. You should either implement equals and hashCode or, faster but possibly more error prone, replace the when(...) definition with:

when(restaurantService.findByNameIgnoreCaseContaining(refEq(filter)))
    .thenReturn(Arrays.asList(first, second));

Matchers.refEq will compare the values using reflection and does not rely on .equals comparison.

Miloš Milivojević
  • 5,219
  • 3
  • 26
  • 39