Spring injecting an initiallized mock via constructor

Viewed 331

I have a singleton class (so private constructor) which needs to use a Spring Data repository during initialization. I have one injected as a constructor argument. Roughly:

@Controller
public class MyClass {
    @Autowired
    private MyClass(MyRepository repo) {
        repo.findAll();
    }
}

I want to unit test my class, so I need to have a mock repository initialized with mock values and then passed into my class before my class is initialized. How do I write my Mockito mocks in my JUnit test to make this possible?

3 Answers

You don't need Spring; this is an advantage of constructor injection. Just use MyRepository mockRepo = mock(MyRepository.class) and new MyClass(mockRepo).

(Your constructor should be public, by the way. You seem to be making the common mistake of confusing different senses of "singleton"; in the case of DI it simply means that the container only makes a single instance and shares it. Finally, if you only have one constructor you don't need @Autowired.)

Unit test should be independent. It means, we are not using real data from database even call any service from our test file.

Assuming you are using JUni5 and have findAllStudents() in your controller. So your test file approximately like this

@TestInstance(TestInstance.Lifecycle.PER_CLASS)
public class MyClassTest {

    @Mock
    private MyRepository repo;
    
    @InjectMocks
    private MyClass controller;

    @BeforeAll
    public void setup() {
        MockitoAnnotations.initMocks(this);
    }

    @Test
    public void testDataIsExist() {
        List<String> expectednames = new ArrayList();
        expectedNames.add("Foo");
        expectedNames.add("Bar");

        Mockito.when(myRepo.findAll()).thenReturn(expectedNames);

        List<String> result = controller.findAllStudents();
        Assertions.assertNotNull(result);
        Assertions.assertEquals(expectednames, result);
    }
}

So we are mock all the services we use in controller, then inject to controller itself. Then inside the test method we mock repo.findAll() to return expectedNames so if the controller find that function it will return what mock says to return.

After we call the function, we have to make it sure that the result is according to what we expected.

There is little value in a "unit test" for a @Controller implementation. If you use @WebMvcTest, then you can use @MockBean:

@WebMvcTest
class MyControllerTest {

  @Autowired
  private MockMvc mockMvc;

  @MockBean
  private MyRepository repository;

  @Test
  void testSomething() {
    mockMvc.perform( ... );
  }
}
Related