1

I have a Spring MVC application. It has Controller, Service and Dao. I would like to test only the Controller and Service by Mocking the DAO layer using Mockito.

My Controller class:

@Controller
@RequestMapping(value="/audit")
public class AuditController {

   @Autowired
   AuditService auditService;

   ...
}

My Service class:

@Service
public class AuditService {

   @Autowired
   AuditDao auditDao;

   ....
}

My Test class:

@RunWith(SptringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"/dispatcher-servlet.xml", "spring-context.xml"})
@WebAppConfiguration
public class AuditControllerTest {

   private MockMvc mockMvc;

   @Mock
   AuditDao auditDao;

   @Autowired
   private WebApplicationContext webApplicationContext;

   @Before
   public void setUp() {
       MockitAnnotations.initMocks(this);
       mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
   }


  @Test
  public void testGetAudit() {

      Mockito.when(auditDao.getAudit(Mockito.any(Long.class))).thenReturn(new Audit(1L));
      mockMvc.perform(get("/audit/{id}", "1")).andExpect(status().isOk());
  }
}

PROBLEM: It performs the call fine by going through autowired controller and Service. However, from the Service the DAO calls are going to a real DAO not the Mocked DAO.

  1. I understand that the DAO is autowired to the real Dao, however I am not sure how to replace that Dao with the Mock one from the Test.

  2. Keeping the Dao in the controller and using @InjectMock to the controller works fine, but I want to keep the Dao in the Service and test only the controller and Service, but mock the Dao alone.

  3. I suspect that this issue is to do with the contexts (web application context and the MockMvc context), however I am not sure how to resolve it.

Any help would be greatly appreciated. Thanks in advance.

KayKay
  • 553
  • 7
  • 22
  • There may be a misunderstanding of some of the terms used, as in my opinion there are some questionable design choices here. I would suggest reviewing the current design to follow a more SOLID approach. – Nkosi Nov 08 '19 at 12:53
  • @Nkosi can you please give some specific examples? – KayKay Nov 08 '19 at 13:20
  • Ok with the name change, it changes my original statement about the terms used. With regards to my design statement, `Autowired` fields hide the classes dependencies. You assessment of the actual dao being called is accurate so I am looking into that. – Nkosi Nov 08 '19 at 13:55
  • Wait. Did you try `@InjectMock` on the Service? – Nkosi Nov 08 '19 at 13:59
  • In the mean time some interesting reading https://tedvinke.wordpress.com/2014/02/13/mockito-why-you-should-not-use-injectmocks-annotation-to-autowire-fields/ – Nkosi Nov 08 '19 at 14:02
  • Does using @InjectMock on the service from the Test class has any effect? Since I won't be using that service entity at all in my Tests. All calls will be going through the Controller via MockMvc.perform as you can see in the code mentioned. – KayKay Nov 08 '19 at 14:13
  • I am not sure off hand. Try it and see. It is what I was thinking though. – Nkosi Nov 08 '19 at 14:15

1 Answers1

1

First I would suggest avoiding Autowired fields and have you class explicitly expose their dependencies via constructor dependencies

Controller class:

@Controller
@RequestMapping(value="/audit")
public class AuditController {
    private final AuditService auditService;

    @Autowired
    public AuditController(AuditService auditService) {
        this.auditService = auditService
    }

    //...
}

Service class:

@Service
public class AuditService {            
    private final AuditDao auditDao;

    @Autowired
    public AuditService(AuditDao auditDao) {
        this.auditDao = auditDao;
    }

    //....
}

I was thinking of something along the line of

@RunWith(SptringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"/dispatcher-servlet.xml", "spring-context.xml"})
@WebAppConfiguration
public class AuditControllerTest {

    private MockMvc mockMvc;

    @Mock
    AuditDao auditDao;

    @InjectMock
    AuditService auditService;

    @Before
    public void setUp() {
        MockitAnnotations.initMocks(this);
        AuditController controller = new AuditController (auditService);
        mockMvc = MockMvcBuilders.standaloneSetup(controller).build();
    }

    @Test
    public void testGetAudit() {

      Mockito.when(auditDao.getAudit(Mockito.any(Long.class))).thenReturn(new Audit(1L));
      mockMvc.perform(get("/audit/{id}", "1")).andExpect(status().isOk());
    }
}

But am uncertain if it will behave as expected when exercised.

Nkosi
  • 235,767
  • 35
  • 427
  • 472
  • I have tried and it still calls the real DAO not the mock DAO. – KayKay Nov 08 '19 at 14:40
  • I think the real issue here is the DAO is autowired to the real Dao outside the MockMvc context, we need to see how to set it to the mock dao inside the MockMvc context, which I am unsure of. Using Mockito.mock inside the test methods are also not having any impact though. – KayKay Nov 08 '19 at 14:41
  • You need to override the bean in the context. There seem to be various options. One option would seem to be to create some new test only spring configuration which defines a new mock auditDao. If this is marked as 'primary' then the fact there will be 2 instances loaded shouldn't be a problem. Don't have time to provide a full solution right now. – Alan Hay Nov 08 '19 at 17:44
  • @Nkosi Awesome, your updated solution did the trick, and it makes sense. Thanks loads. – KayKay Nov 10 '19 at 08:23