Should Mockito be used with MockMvc's webAppContextSetup in Spring 4?

二次信任 提交于 2019-12-04 15:23:28

You might be interested in the new testing features coming in Spring Boot 1.4 (specifically the new @MockBean annotation). This sample shows how a service can be mocked and used with a controller test.

For some reason the Mockito annotations @Mock et @InjectMocks won't work in this case.

Here's how I managed to make it work :

  • Instantiate the personService bean manually using your own Test context
  • make Mockito create a mock for this personService.
  • let Spring inject these mocks in the controller PersonController.

You should have your TestConfig :

@Configuration
public class ControllerTestConfig {

  @Bean
  PersonService personService() {
    return mock(PersonService.class);
  }

}

In your PersonControllerTest, you won't need the personController anymore, since it's managed by the mockMvc through the perform method. You also don't need to execute initMocks() because you initialize your mocks manually inside the Spring config. You should have something like :

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = {Application.class, ControllerTestConfig.class})
@WebAppConfiguration
public class PersonControllerTest {

  private MockMvc mockMvc;

  @Autowired
  private WebApplicationContext webApplicationContext;

  @Autowired
  PersonService personService;

  @Before
  public void setup() {
    mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
  }

  @Test
  public void getPersonById() throws Exception {
    Long id = 999L;
    String name = "Person name";

    when(personService.findById(id)).thenReturn(new PersonModel(id, name));

    mockMvc.perform(get("/person/getPersonById/" + id))
        .andDo(print())
        .andExpect(jsonPath("$.id", is(toIntExact(id))))
        .andExpect(jsonPath("$.name", is(name)));
  }
}

I sometimes use Mockito to fake Spring beans with usage of @Primary and @Profile annotations. I wrote a blog post about this technique. It also contains link to fully working example hosted on GitHub.

To extend florent's solution, I encountered performance issues and extensibility issues creating separate configurations for every controller test which needed a different set of service mocks. So instead, I was able to mock out my application's service layer by implementing a BeanPostProcessor alongside my tests which replaces all @Service classes with mocks:

@Component
@Profile("mockService")
public class AbcServiceMocker implements BeanPostProcessor {

  private static final String ABC_PACKAGE = "com.mycompany.abc";

  @Override
  public Object postProcessBeforeInitialization(Object bean, String name) throws BeansException {
    if (StringUtils.startsWith(bean.getClass().getPackage().getName(), ABC_PACKAGE)) {
      if (AnnotationUtils.isAnnotationDeclaredLocally(Service.class, bean.getClass())
          || AnnotationUtils.isAnnotationInherited(Service.class, bean.getClass())) {
        return mock(bean.getClass());
      }
    }
    return bean;
  }

  @Override
  public Object postProcessAfterInitialization(Object bean, String name) throws BeansException {
    return bean;
  }
}

I enabled these mocks in specific tests with an @ActiveProfiles annotation:

@WebAppConfiguration
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration({"classpath:/WEB-INF/application-context.xml"})
@ActiveProfiles("mockService")
public class AbcControllerTest {

  private MockMvc mvc;

  @Before
  public final void testBaseSetup() {
    mvc = MockMvcBuilders.webAppContextSetup(context).build();
  }

Lastly, the injected Mockito mocks were wrapped in an AopProxy causing Mockito's expect and verify calls to fail. So I wrote a utility method to unwrap them:

  @SuppressWarnings("unchecked")
  protected <T> T mockBean(Class<T> requiredType) {
    T s = context.getBean(requiredType);
    if (AopUtils.isAopProxy(s) && s instanceof Advised) {
      TargetSource targetSource = ((Advised) s).getTargetSource();
      try {
        return (T) targetSource.getTarget();
      } catch (Exception e) {
        throw new RuntimeException("Error resolving target", e);
      }
    }
    Mockito.reset(s);
    return s;
  }
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!