Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spring + Mockito test injection

Tags:

spring

mockito

My question is very similar to the issue raised in Injecting Mockito mocks into a Spring bean. In fact, I believe the accepted answer there might actually work for me. However, I've got one issue with the answer, and then some further explanation in case the answer there is not in fact my answer.

So I followed the link in the aforementioned post to the Springockito website. I altered my test-config.xml to include something similar to the following:

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:mockito="http://www.mockito.org/spring/mockito"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
    http://www.mockito.org/spring/mockito http://www.mockito.org/spring/mockito.xsd">

...

    <mockito:mock id="accountService" class="org.kubek2k.account.DefaultAccountService" />
...
</beans>

There seems to be something wrong with the www.mockito.org redirect currently, so I found the XSD code at https://bitbucket.org/kubek2k/springockito/raw/16143b32095b/src/main/resources/spring/mockito.xsd and altered the final entry in xsi:schemaLocation to point to this bitbucket link.

Running mvn test then produced the following error (newlines added for readability):

Caused by: org.springframework.beans.factory.xml.XmlBeanDefinitionStoreException:
    Line 43 in XML document from class path resource [spring/test-context.xml] is invalid;
    nested exception is org.xml.sax.SAXParseException; lineNumber: 43; columnNumber: 91;
    The prefix "mockito" for element "mockito:mock" is not bound.

So the question regarding Springockito is: Is it possible anymore to include this? What am I missing?

Now, on to the further explanation...

I have an interface whose implementation I'm trying to test:

public interface MobileService {
    public Login login(Login login);
    public User getUser(String accessCode, Date birthDate);
}

The implementation contains a DAO that Spring @Autowires in for me:

@Service
public class MobileServiceImpl implements MobileService {
    private MobileDao mobileDao;

    @Autowired
    public void setMobileDao(MobileDao mobileDao) {
        this.mobileDao = mobileDao;
    }
}

I don't want to alter my interface to include a setMobileDao method, because that would be adding code just to support my unit testing. I'm trying to mock out the DAO since the actual SUT here is the ServiceImpl. How can I achieve this?

like image 850
Mike Avatar asked Oct 18 '11 21:10

Mike


2 Answers

You don't want to test your interface: it contains no code at all. You want to test your implementation. So the setter is available. Just use it:

@Test
public void testLogin() {
    MobileServiceImpl toTest = new MobileServiceImpl();
    toTest.setMobileDao(mockMobileDao);
    // TODO call the login method and check that it works as expected.
}

No need for a spring context. Just instanciate your POJO service, inject mock dependencies manually, and test the methods you want to test.

like image 173
JB Nizet Avatar answered Oct 19 '22 14:10

JB Nizet


After struggling with the Springockito XSD issue, for a while, I found a much simpler solution. Let Spring inject the mock for you using a factory method, i.e. in applicationContext.xml put:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

  <bean class="org.mockito.Mockito" factory-method="mock">
    <constructor-arg value="com.gerrydevstory.mycoolbank.AccountsDAO"/>
  </bean>

  <bean class="com.gerrydevstory.mycoolbank.BankingService"/>

</beans>

where the AccountsDAO bean is injected into the BankingService class. The corresponding JUnit test case is:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("/BankingServiceTest.xml")
public class BankingServiceTest {

  @Autowired private BankingService bankingService;
  @Autowired private AccountsDAO mockAccountsDAO;

  @Test
  public void testTransfer() throws Exception {
    // Setup 2 accounts
    Account acc1 = new Account();
    acc1.setBalance(800.00);
    Account acc2 = new Account();
    acc2.setBalance(200.00);

    // Tell mock DAO to return above accounts when 1011 or 2041 is queried respectively
    when(mockAccountsDAO.findById(1011)).thenReturn(acc1);
    when(mockAccountsDAO.findById(2041)).thenReturn(acc2);

    // Invoke the method to test
    bankingService.transfer(1011, 2041, 500.00);

    // Verify the money has been transferred
    assertEquals(300.00, acc1.getBalance(), 0.001);
    assertEquals(700.00, acc2.getBalance(), 0.001);
  }
}

Personally I find this very elegant and easy to understand. For more details, see the original blog post.

like image 1
Erica Kane Avatar answered Oct 19 '22 14:10

Erica Kane