Nuñito Calzada
Nuñito Calzada

Reputation: 1950

Testing REST services with Spring MVC and mocking services

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()

Upvotes: 1

Views: 1769

Answers (1)

jmaniquet
jmaniquet

Reputation: 103

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

Upvotes: 2

Related Questions