40

I am testing a business service with TestNG, mockito unit tests in spring boot application.

Application is multi-module spring boot project.And I am writing unit tests for business module.

I have added following dependencies related testing in pom,

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-test</artifactId>
  <scope>test</scope>
</dependency>
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-data-jpa</artifactId>
   <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.testng</groupId>
    <artifactId>testng</artifactId>
    <version>${testng.version}</version>
    <scope>test</scope>
 </dependency>
 <dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-core</artifactId>
    <scope>test</scope>
 </dependency>
 <dependency>
     <groupId>org.hsqldb</groupId>
     <artifactId>hsqldb</artifactId>
     <scope>test</scope>
 </dependency>
 <dependency>
     <groupId>org.hibernate</groupId>
     <artifactId>hibernate-validator</artifactId>
     <scope>test</scope>
 </dependency>
 <dependency>
     <groupId>javax.el</groupId>
     <artifactId>el-api</artifactId>
     <version>${javaxel.version}</version>
     <scope>test</scope>
 </dependency>
 <dependency>
      <groupId>org.glassfish</groupId>
      <artifactId>javax.servlet</artifactId>
      <version>${javax.servlet.version}</version>
      <scope>test</scope>
 </dependency>

My wrapper annotation look like

@Service
@Transactional
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface MyServiceAnnotation{}

My TestApp looks like

@SpringBootApplication
public class TestApp{ .... }

My Business Service looks like

@MyServiceAnnotation
public class AddressServiceImpl implements AddressService {
       @Autowire
       UserDAO userDAO;
       @Autowire
       AddressDAO addressDAO;

       public Address find(int userId) {
              user =  userDAO.findOne(userId);
              /** if I run following test then I get user NULL.
                  But it should get user object which I have created
                  in data provider 
               **/
              if(user == null ) { throw new BadReqExcp("invalid user Id", 101); }
              address = user.findAddresses();
              if(address is empty) { throw new BadReqExcp("add not found", 102);}
              return address;
       }
}

MyTestClass looks like

@ContextConfiguration(classes = { TestApp.class })
class MyTestClass{ 
    @Mock
    UserDAO userDAO;

    @InjectMocks
    @Autowire
    AddressService addressServie;

    @BeforeMethod
    public void initMock() {
        MockitoAnnotations.initMocks(this);
    }

    @Test(dataProvider = "getUser", dataProviderclass = UserDP.class)
    public void shouldThrowExceptionAddressNotFound(int userId, User user)
    {
        when(userDAO.findOne(userId)).thenReturn(user);  //here dao call should return user but it is returning null
         try{
              addressService.find(userId);
         }
         catch(BadReqExcp e){
              // Here errro code should be 102 but fount 101
               assertEquals(e.getErrorCode(), 102);
         }
    }
}

If I don't use @Target(ElementType.TYPE), @Retention(RetentionPolicy.RUNTIME), @Inherited these annotations then my mock DAO calls in test works fine.

I need above annotations explicitly because if I do not use them then,

For example, If I want to perform one single task which uses multiple business service then they wont happen in ONE transaction. In other words if a business call uses multiple business services say ServiceA and ServiceB. Call goes from serviceA to serviceB. If an exception occurs in serviceB then database changes done by serviceA wont rollback.

When I use above annotations then above example works BUT mock DAO calls in junit tests does not works.

Do I have wrong dependencies in pom?

  1. Why this is not working ?
  2. What would be the solution over it ?

Git Repository Source Code , here you will get sample code.It is giving me some error while compiling.

Roman C
  • 49,761
  • 33
  • 66
  • 176
Prashant Shilimkar
  • 8,402
  • 13
  • 54
  • 89

4 Answers4

7

I recommend you to keep tests simple. You can take profit of DI benefits. For further details please visit Spring documentation:

One of the major advantages of dependency injection is that it should make your code easier to unit test. You can simply instantiate objects using the new operator without even involving Spring. You can also use mock objects instead of real dependencies.

Your test class should look like this.

public class AddressTest {

    @Mock
    private UserDAO userDAO;

    @Mock
    private AddressDAO addressDAO;

    @InjectMocks
    private AddressService addressServie;

    @BeforeMethod
    public void initMock() {
        addressServie = new AddressServiceImpl();
        MockitoAnnotations.initMocks(this);
    }

    @Test(dataProvider = "getUser", dataProviderClass = UserDP.class)
    public void shouldThrowExceptionAddressNotFound(int userId, User user) {
        when(userDAO.findOne(userId)).thenReturn(user);
        try {
            addressServie.findAllAddress(userId);
        } catch (BadRequestException badRequestException) {
            assertEquals(badRequestException.getErrorCode(), 102);
        }
    }
}

You should also check for null address list in your implementation. The test fails because provider class provides the test with a user instance that does not have address list initialized.

@Override
public List<Address> findAllAddress(int userId) {
    User user = userDAO.findOne(userId);
    if (user == null) {
        throw new BadRequestException("Invalid user id", 101);
    }
    List<Address> addresses = user.getAddresses();
    if (addresses == null || addresses.isEmpty()) {
        throw new BadRequestException("Address Not found", 102);
    }
    return addresses;
}
alxgarcia
  • 594
  • 2
  • 4
  • Just one thing, why they work if @Target(ElementType.TYPE), @Retention(RetentionPolicy.RUNTIME), @Inherited not used. – Prashant Shilimkar Aug 28 '15 at 07:19
  • The test gets rid of all that and just tests the class method. Since an instance of a class is initialized and Mockito injects fake dependencies (using @Mock and @InjectMocks), you don't need anything else. All annotations you mention become relevant once the application is running on the server. – alxgarcia Aug 28 '15 at 08:17
2

Remove all your annotations. You need something special to make the transactions work.

Problem:

Call goes from serviceA to serviceB. If an exception occurs in serviceB then database changes done by serviceA wont rollback

Spring’s transaction manager provides a technology-independent API that allows you to start a new transaction by calling the getTransaction() method and manage it by

commit()
rollback()

As PlatformTransactionManager is an abstract unit for transaction Management,

the methods you called for transaction management are guaranteed to be technology independent.

    import org.springframework.dao.DataAccessException;
    import org.springframework.jdbc.core.support.JdbcDaoSupport;
    import org.springframework.transaction.PlatformTransactionManager;
    import org.springframework.transaction.TransactionDefinition;
    import org.springframework.transaction.TransactionStatus;
    import org.springframework.transaction.support.DefaultTransactionDefinition;
    public class TransactionalJdbcBookShop extends JdbcDaoSupport implements BookShop {
    @Autowired
    private PlatformTransactionManager transactionManager;

.....

then inside your dao method you can configure commit and rollback method.

    public void purchase(String isbn, String username) {
    TransactionDefinition def = new DefaultTransactionDefinition();
    TransactionStatus status = transactionManager.getTransaction(def);
    try {
    //Your query over here
    transactionManager.commit(status);
    } catch (DataAccessException e) {
    //if the above query fails then
    transactionManager.rollback(status);
    throw e;
    }
    }

A transactionmanager is declared in the XML configuration file as a normal bean.

For example,

the following bean configuration declares a DataSourceTransactionManager instance.

It requires the dataSource property to be set so that it can manage transactions for connectionsmade by this data source.

<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<bean id="bookShop"
class="com.apress.springrecipes.bookshop.TransactionalJdbcBookShop">
<property name="dataSource" ref="dataSource" />
<property name="transactionManager" ref="transactionManager" />
</bean>

How can I use Spring Boot auto-configured beans in XML configuration files?

You can also go through the github to implement bean inside your app over here

Once you have a transaction definition,

You can ask the transaction manager to start a new transaction with that definition by calling the getTransaction() method.

Then it will return a TransactionStatus object to keep track of the transaction status.

If all the statements execute successfully, you ask the transaction manager to commit this transaction by passing in the transaction status.

As all exceptions thrown by the Spring JDBC template are subclasses of DataAccessException, you ask the transactionmanager to roll back the transaction when this kind of exception is caught.

In this class, you have declared the transaction manager property of the general type PlatformTransactionManager.

Now you have to inject an appropriate transaction manager implementation.

As you are dealing with only a single data source and accessing it with JDBC, you should choose DataSourceTransactionManager.

Community
  • 1
  • 1
MS Ibrahim
  • 1,789
  • 1
  • 16
  • 28
2

Can you try using MockitoJUnitRunner.

@RunWith(MockitoJUnitRunner.class)
@ContextConfiguration(classes = { TestApp.class })
class MyTestClass{ 
..
}
Arpit Aggarwal
  • 27,626
  • 16
  • 90
  • 108
1

I think that problem may be caused by annotation processing order.

You probably can try to set internal state of your service explicitly in before method like so:

@Mock
UserDAO userDAO;

@Autowire
AddressService addressServie;

@BeforeMethod
public void initMock() {
    MockitoAnnotations.initMocks(this);
    // using mockito Whitebox
    org.mockito.internal.util.reflection.Whitebox.setInternalState(addressServie, "userDAO", userDAO);
    /* or using spring test method
    org.springframework.test.util.ReflectionTestUtils.setField(addressServie, "userDAO", userDAO);*/
}

and check whether error still occurs

Alex
  • 7,460
  • 2
  • 40
  • 51
  • It is saying "Runtime Unable to set internal state". As have using SpringBootApplication for test. Is it a problem though I just need a context.How can I use just a context wihtout SpringBoot application. – Prashant Shilimkar Aug 06 '15 at 06:13