Переменная mockproductService не вводится из контекста приложения и содержит null значений при использовании аннотации @Mock и получении ошибки актива.
Ошибка утверждения, с которой я сейчас сталкиваюсь, выглядит следующим образом:
java.lang.AssertionError: Model attribute 'Products' expected:<[com.pointel.spring.test.Product@e1b42, com.pointel.spring.test.Product@e1f03]> but was:<[]>
at org.springframework.test.util.AssertionErrors.fail(AssertionErrors.java:60)
at org.springframework.test.util.AssertionErrors.assertEquals(AssertionErrors.java:89)
at org.springframework.test.web.servlet.result.ModelResultMatchers$2.match(ModelResultMatchers.java:68)
at org.springframework.test.web.servlet.MockMvc$1.andExpect(MockMvc.java:141)
at com.pointel.spring.test.ProductControllerTest.testMethod(ProductControllerTest.java:84)
Примечание. Если я использую @Autowired вместо @Mock, все работает нормально.
Класс контроллера тестирования
RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextConfiguration(locations={"classpath:mvc-dispatcher-servlet.xml"})
@TestExecutionListeners({ DependencyInjectionTestExecutionListener.class})
public class ProductControllerTest {
@Autowired
private WebApplicationContext wac;
private MockMvc mockMvc;
@InjectMocks
private ProductController productController;
@Mock
//@Autowired
private ProductService mockproductService;
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
List<Product> products = new ArrayList<Product>();
Product product1 = new Product();
product1.setId(new Long(1));
Product product2 = new Product();
product2.setId(new Long(2));
products.add(product1);
products.add(product2);
Mockito.when(mockproductService.findAllProducts()).thenReturn(products);
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
}
@Test
public void testMethod() throws Exception {
List<Product> products = new ArrayList<Product>();
Product product1 = new Product();
product1.setId(new Long(1));
Product product2 = new Product();
product2.setId(new Long(2));
products.add(product1);
products.add(product2);
RequestBuilder requestBuilder = MockMvcRequestBuilders.get("/products");
this.mockMvc.perform(requestBuilder).
andExpect(MockMvcResultMatchers.status().isOk())
.andExpect(MockMvcResultMatchers.model().attribute("Products", products))
//.andExpect(MockMvcResultMatchers.model().size(2))
.andExpect(MockMvcResultMatchers.view().name("show_products"));
}
}
Класс контроллера
@Controller
public class ProductController {
@Autowired
private ProductService productService;
@RequestMapping("/products")
public String testController(ModelMap model){
model.addAttribute("Products",productService.findAllProducts());
return "show_products";
}
}
Для меня неясно, как комбинация Spring и Mockito, которую вы взяли из источника блога, на которую ссылаетесь, должна работать вообще так, как ожидалось. По крайней мере, я могу объяснить ваше наблюдение:
Ваш тест (this.mockMvc.perform()) работает с контекстом веб-приложения, созданным Spring. В этом контексте ProductController был создан Spring (context:component-scan). Затем productService был автоматически связан с макетом Mockito, который вы создали в mvc-dispatcher-servlet.xml как someDependencyMock.
Если вы внедряете mockproductService через @Autowired, Spring вставляет экземпляр someDependencyMock из своего контекста. Таким образом, ваш вызов Mockito.when() работает правильно на этом экземпляре, который уже был правильно подключен к ProductController, как упоминалось ранее.
Но если вы внедрите mockproductService через @Mock, Mockito вставит новый экземпляр ProductService, а не контекст Spring, поскольку он вообще ничего не знает о Spring. Таким образом, ваш вызов Mockito.when() не работает с макетом, который был автоматически подключен Spring, и поэтому someDependencyMock остается неинициализированным.
Итак, мне осталось неясным, как вообще работал исходный код из блога:
Свойство productController с аннотацией @InjectMocks будет инициализировано Mockito и даже правильно подключено к mockproductService в тестовом классе. Но Spring ничего не знает об этом объекте и не будет использовать его в вызовах this.mockMvc.perform(). Поэтому я предполагаю, что если вы вводите mockproductService только с @Autowired, ваш тест работает так, как предполагалось, даже если вы удалите как свойство productController, так и вызов MockitoAnnotations.initMocks() в своем тестовом классе.
Ответ 2 · рейтинг 3 · Cebence
Я не смотрел учебник, который вы упомянули, потому что код, который вы предоставили, достаточно говорит об опыте или его отсутствии у оригинального автора.
Общее правило тестирования заключается в том, что вы не смешиваете разные типы тестов. Первый уровень тестирования - это модульные тесты. Это означает, что вы тестируете отдельную единицу работы (обычно один класс). После прохождения модульных тестов вы пишете интеграционные тесты, которые объединяют определенные компоненты (классы) и проверяют их совместную работу.
Класс редко ни от чего не зависит, поэтому для создания действительно хорошего модульного теста необходимо макетировать все его зависимости.
@RunWith(MockitoJUnitRunner.class)
public class ProductControllerTest {
@Mock private ProductService mockproductService;
@InjectMocks private ProductController productController;
@Test
public void testMethod() throws Exception {
// Prepare sample test data.
final Product product1 = Mockito.mock(Product.class);
final Product product2 = Mockito.mock(Product.class);
final ArrayList<Product> products = new ArrayList<Product>();
products.add(product1);
products.add(product2);
final ModelMap mmap = Mockito.mock(ModelMap.class);
// Configure the mocked objects.
Mockito.when(product1.getId()).thenReturn(new Long(1));
Mockito.when(product2.getId()).thenReturn(new Long(2));
Mockito.when(mockproductService.findAllProducts()).thenReturn(products);
final mmap = Mockito.mock(ModelMap.class);
// Call the method under test.
final String returned = productController.testController(mmap);
// Check if everything went as planned.
Mockito.verify(mmap).addAttribute("Products", products);
assertNotNull(returned);
assertEquals("show_products", returned);
}
}
Вот как должен выглядеть юнит-тест. Сначала вы подготавливаете данные (объекты) - обратите внимание, что все они издеваются. Кроме того, использование final предотвращает случайные присвоения, то есть случайную перезапись существующего значения.
Во-вторых, вы настраиваете поведение каждого имитируемого объекта. Если для идентификатора будет запрошен Product, вы указываете, что в этом случае вернет издевательский экземпляр. Кстати, я действительно не вижу смысла устанавливать эти идентификаторы продуктов, поэтому первая часть теста может выглядеть так:
final Product product1 = Mockito.mock(Product.class);
final Product product2 = Mockito.mock(Product.class);
final ArrayList<Product> products = new ArrayList<Product>();
products.add(product1);
products.add(product2);
final mmap = Mockito.mock(ModelMap.class);
// Configure the mocked objects.
Mockito.when(mockproductService.findAllProducts()).thenReturn(products);
final mmap = Mockito.mock(ModelMap.class);
В-третьих, вызовите тестируемый метод и сохраните его результат:
final String returned = productController.testController(mmap);
И, наконец, вы проверяете, вел ли тестируемый класс ожидаемое поведение. В этом случае метод addAttribute()ModelMap должен был быть вызван именно с этими точными значениями параметров. И возвращаемая строка не должна быть null, а должна быть "show_products" - обратите внимание на порядок параметров метода assertEquals(expected, actual), потому что в случае неудачного теста JUnit распечатает сообщение "Ожидал ЭТО, но получил ЭТО".
@RunWith(MockitoJUnitRunner.class)
public class ProductControllerTest {
@Mock private ProductService mockproductService;
@InjectMocks private ProductController productController;
Чтобы @InjectMocks работал как @Autowired Spring, тест должен быть запущен с классом MockitoJUnitRunner - он найдет все @Mock элементы, создаст их и внедрит нужные в член, отмеченный @InjectMocks.
Ответ 3 · рейтинг 3 · Sparm
Я думаю, что есть проблема с ответом, который предоставил @Cebence, в том, что он не учитывает использование OP spring-webmvc-test @WebApplication. Если бы вы запустили пример, предоставленный с
@RunWith(MockitoJUnitRunner.class)
а у тебя еще есть
@Autowired private WebApplicationContext wac;
Тогда тест провалится. У меня была та же проблема, что и у @Human Being, и я нашел простое решение - настроить службу в контроллере как не требуемую. Не идеально, но вот решение:
Контроллер:
@Controller
public class MyController
{
@Autowired(required=false)
MyService myService;
.
.
.
}
Тест:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations="classpath:/META-INF/spring/applicationContext-test.xml")
@WebAppConfiguration
public class MyControllerTest
{
// This is the backend service we are going to mock
@Mock
MyService myService;
// This is the component we are testing and we inject our mocked
// objects into it
@InjectMocks
@Resource
private MyController myController;
@Autowired
private WebApplicationContext wac;
private MockMvc mockMvc;
@Before
public void setup()
{
MockitoAnnotations.initMocks(this);
this.mockMvc = webAppContextSetup(this.wac).build();
List<Object> data = new ArrayList<Object>();
// Mock one of the service mthods
when(myService.getAll()).thenReturn(datasets);
}
@Test
public void testQuery() throws Exception
{
this.mockMvc.perform(get("/data").accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(content().contentType("application/json;charset=UTF-8"))
.andExpect(jsonPath("$.value").value("Hello"));
}
}