Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spring Mockito - Junit Controller Test - Mock one service

I have a controller class ControllerClass that manages two class services:

  • ServiceA that parse some files

  • ServiceB that manage the filesystem

I want to test ControllerClass and in particular:

  • ServiceA Autowired class

  • ServiceB Mock this service, with a mock class that implement the intercace returning always the fixed value.

How can I do?

like image 583
michele Avatar asked Feb 05 '19 16:02

michele


2 Answers

Starting from a simple spring-web application with the following test:

@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
public class MyControllerTest {

    @Autowired
    private MockMvc mvc;

    @Test
    public void testGreeting() throws Exception {
        mvc.perform(MockMvcRequestBuilders.get("/").accept(MediaType.APPLICATION_JSON))
                .andExpect(status().isOk())
                .andExpect(content().string(equalTo("A response: I am real ServiceA!, B response: I am real ServiceB!")));
    }
}

Solution I

Using @MockBean & @Before:

@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
public class MyControllerMockBeanTest {

    @MockBean
    private ServiceB mockB;

    @Before
    public void setup() {
       Mockito.when(mockB.greeting()).thenReturn("I am mock Service B!");
    }
    @Autowired
    private MockMvc mvc;

    @Test
    public void testGreetingMock() throws Exception {
       mvc.perform(MockMvcRequestBuilders.get("/")
            .accept(MediaType.APPLICATION_JSON))
            .andExpect(status().isOk())
            .andExpect(content().string(equalTo("A response: I am real ServiceA!, B response: I am mock Service B!")));
    }
}

Solution II

Using spring profiles & custom test config:

@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
// activate "test" profile
@ActiveProfiles("test")
// set custom config classes (don't forget Application)
@ContextConfiguration(classes = {TestConfig.class, Application.class})
public class MyControllerTest {
    // define configuration for "test" profile (inline possible)
    @Profile("test")
    @Configuration
    static class TestConfig {

        @Bean
        // !
        @Primary
        // I had an (auto configuration) exception/clash, 
        // when using *same bean name*, so *not* 'serviceB()', plz.
        public ServiceB mockB() {
            // prepare...
            ServiceB mockService = Mockito.mock(ServiceB.class);
            Mockito.when(mockService.greeting()).thenReturn("I am Mock Service B!");
            // and return your mock object!
            return mockService;
        }
    }
    @Autowired
    private MockMvc mvc;

    @Test
    public void testGreetingMock() throws Exception {
        mvc.perform(MockMvcRequestBuilders.get("/").accept(MediaType.APPLICATION_JSON))
                .andExpect(status().isOk())
                .andExpect(content().string(equalTo("A response: I am real ServiceA!, B response: I am Mock Service B!")));
    }
}

Complete sample at github.


And I am quite sure, that the list of solutions is not complete.

...

like image 125
xerx593 Avatar answered Nov 11 '22 02:11

xerx593


@MockBean looks like good candidate for your use-case.

This annotations behaves in following way:

  • if no bean is defined in test context - new one will be added
  • if single bean is already defined - then it will be replaced by the mock
  • if two or more beans are already defined - then use @Qualifier to specify which one should be replaced by mock

After you wired mock bean in your test class, you can stub it, to always return some value. For example, for specific test just add something like this to your test:

    @Autowired
    private ServiceA serviceA;
    @MockBean
    private ServiceB serviceB;

    @Test
    public void testSomething() {
        when(serviceB.doSomething()).thenReturn("fixed response");
        // ...
    }

If you want stub for all tests - put the stubbing in setup method:

    @Autowired
    private ServiceA serviceA;
    @MockBean 
    private ServiceB serviceB;

    @Before
    public void setup() {
        when(serviceB.doSomething()).thenReturn("fixed response");
    }

By the way, Spring also provies @SpyBean with similar behaviour as @MockBean.

Basically there is no difference between spy and mock, if you stub method calls on them. The difference become apperent, when method calls are not stubbed:

  • in case of mock - nothing is done (if method returns void - it just not invoked, if method returns something - then mock will return null)
  • in case of spy - method of real object is invoked.
like image 34
Oleksii Zghurskyi Avatar answered Nov 11 '22 04:11

Oleksii Zghurskyi