Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

TestNG unit test not working after annotating service to test with @Retention, @Transactional, @Inherited

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.

like image 454
Prashant Shilimkar Avatar asked Jul 09 '15 10:07

Prashant Shilimkar


People also ask

Which annotation will execute the method after each test in TestNG?

@AfterMethod: This will be executed after every @test annotated method.

Which annotation is mandatory in TestNG test?

The @AfterClass annotated method will be invoked after the execution of all the test methods of the current class. The @BeforeMethod annotated method will be executed before each test method will run. The @AfterMethod annotated method will run after the execution of each test method.

How do I enable test in TestNG?

TestNG @Test enable parameter. You can disable or exclude the test cases by using the enable attribute to the @Test annotation and assign False value to the enable attribute.

Which TestNG annotation is used to disable tests?

Sometimes, it happens that our code is not ready and the test case written to test that method/code fails. In such cases, annotation @Test(enabled = false) helps to disable this test case.


3 Answers

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;
}
like image 173
alxgarcia Avatar answered Oct 17 '22 12:10

alxgarcia


Can you try using MockitoJUnitRunner.

@RunWith(MockitoJUnitRunner.class)
@ContextConfiguration(classes = { TestApp.class })
class MyTestClass{ 
..
}
like image 2
Arpit Aggarwal Avatar answered Oct 17 '22 13:10

Arpit Aggarwal


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.

like image 2
MS Ibrahim Avatar answered Oct 17 '22 13:10

MS Ibrahim