使用Spring MVC测试REST服务并模拟服务。

huangapple go评论73阅读模式
英文:

Testing REST services with Spring MVC and mocking services

问题

我有一个Spring MVC应用程序。我想测试这个控制器:

    @RunWith(SpringRunner.class)
    @WebAppConfiguration
    @ContextConfiguration(locations = {
    		"classpath:testDatabaseContext.xml"
    })
    public class TimeControllerTests {
    
        @Autowired
        private WebApplicationContext wac;
    
        private MockMvc mockMvc;
    
        @Mock
    	private AutorisationService autorisationService;
    
        @Before
        public void setup() {
            this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
        }
        
        @Test
        public void should_OK() throws Exception {
    
    doReturn(User.builder().build()).when(autorisationService).findById(any(Date.class),anyLong(), 1L);
    
            mockMvc.perform(get("/time/2")
                    .contentType(APPLICATION_JSON))
                    .andExpect(status().isOk());
        }
    }

但是当我运行测试时,我得到了这个错误:

    org.mockito.exceptions.misusing.NullInsteadOfMockException: 
    传递给 when() 的参数是 null!
    正确存根示例:
        doThrow(new RuntimeException()).when(mock).someMethod();
    另外,如果你使用了 @Mock 注解,不要忘记 openMocks()
英文:

I have a Spring MVC application . I want to test this controller:

@RunWith(SpringRunner.class)
@WebAppConfiguration
@ContextConfiguration(locations = {
		"classpath:testDatabaseContext.xml"
})
public class TimeControllerTests {

    @Autowired
    private WebApplicationContext wac;

    private MockMvc mockMvc;

    @Mock
	private AutorisationService autorisationService;

    @Before
    public void setup() {
        this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
    }
    
    @Test
    public void should_OK() throws Exception {

doReturn(User.builder().build()).when(autorisationService).findById(any(Date.class),anyLong(), 1L);

        mockMvc.perform(get("/time/2")
                .contentType(APPLICATION_JSON))
                .andExpect(status().isOk());
    }
}

but when I run the test I have this error:

org.mockito.exceptions.misusing.NullInsteadOfMockException: 
Argument passed to when() is null!
Example of correct stubbing:
    doThrow(new RuntimeException()).when(mock).someMethod();
Also, if you use @Mock annotation don't miss openMocks()

答案1

得分: 1

我看到有几个原因导致这不起作用:

A - 仅有@Mock是不足以初始化一个模拟对象的。你需要:

  • 在setup中调用MockitoAnnotations.initMocks()
  • 移除@Mock,并在setup中调用Mockito.mock(UserRepository.class)

然而,我认为这还不足够,这让我想到:

B - 你的控制器似乎由Spring管理,而你模拟的AutorisationService却没有被管理。

我在这里假设你的控制器使用了一个由Spring注入的AutorisationService。

在这里你有几个选项:

  • 选项1 - 在测试中完全不使用Spring,使用Mockito来管理你的控制器和服务。
    应该类似于:
@InjectMocks
private SomeController someController;

@Mock
private AutorisationService autorisationService;

@Before
public void setup() {
	MockitoAnnotations.initMocks(this);
    this.mockMvc = MockMvcBuilders.standaloneSetup(someController).build();
}

注意standaloneSetup方法:这里的想法是你不使用Spring上下文(顶部的注解可以移除)。

  • 选项2:使用没有Spring Boot的Spring,使用一个模拟的服务。

这有点像一个技巧,但我经常使用它。

你需要提供一个自定义的Spring上下文,其中包含一个由Mockito提供的AutorisationService类型的bean。

// 添加所有其他必要的bean
// 在你的上下下文中可以使用你需要的任何内容:@ComponentScan("somepackage")、@Import(Config1.class, Config2.class)、@ImportResource("context.xml")、@Bean方法...
@Configuration
class SpringWithMockitoConfig {
	@Bean
	AutorisationService autorisationService() {
		return Mockito.mock(AutorisationService.class);
	}
}

然后你的测试将有两个约束:

  • 导入自定义配置:@ContextConfiguration(classes = SpringWithMockitoConfig.class)
  • 在每个测试的setup方法中重置模拟对象:Mockito.reset(autorisationService);

使用这个解决方案,你不需要真实的AutorisationService所依赖的那些bean(我假设是Dao、DataSource等)。

基本上只需要mvc部分。

  • 选项3:使用Spring Boot和@MockBean注解 - 这是我目前不熟悉的。
英文:

I see several reasons why this does not work :

A - @Mock alone is not enough to initialize a mock. You need either :

  • Call MockitoAnnotations.initMocks() in setup
  • Remove @Mock and call Mockito.mock(UserRepository.class) in setup

However I don't think this will be enough, which brings me to :

B - Your controller looks managed by spring, whereas your mocked AutorisationService is not.

I am assuming there that your controller uses an AutorisationService supposedly injected by spring.

You have several options here :

  • Option 1 - Don't use spring at all for your test, and manage your controller and service with mockito.
    This should look like :
@InjectMocks
private SomeController someController;

@Mock
private AutorisationService autorisationService;

@Before
public void setup() {
	MockitoAnnotations.initMocks();
    this.mockMvc = MockMvcBuilders.standaloneSetup(someController).build();
}

Note the standaloneSetup method : the idea here is that you are not using a spring context (annotations at the top can then be removed).

  • Option 2 : Use spring without springboot, with a mocked service.

Kind of a hack, but I use it on regular basis.

You have to provide a custom spring context, with a bean of type AutorisationService, but provided by mockito.

// All all other beans necessary
// You can use whatever you need in your context : @ComponentScan("somepackage"), @Import(Config1.class, Config2.class), @ImportResource("context.xml"), @Bean methods ...
@Configuration
class SpringWithMockitoConfig {
	@Bean
	AutorisationService autorisationService() {
		return Mockito.mock(AutorisationService.class);
	}
}

You will then have two constraint on your tests :

  • Import the custom config : @ContextConfiguration(classes = SpringWithMockitoConfig.class)
  • Reset the mocks on every test in the setup method : Mockito.reset(autorisationService);

With this solution you don't need the beans your real AutorisationService relies on (I assume Dao's, DataSource and such).

Basicly only the mvc part.

  • Option 3 : use SpringBoot and the @MockBean annotation - which I am not familiar with yet

huangapple
  • 本文由 发表于 2020年10月10日 22:20:39
  • 转载请务必保留本文链接:https://go.coder-hub.com/64294430.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定