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?
Git Repository Source Code , here you will get sample code.It is giving me some error while compiling.
@AfterMethod: This will be executed after every @test annotated method.
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.
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.
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.
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;
}
Can you try using MockitoJUnitRunner.
@RunWith(MockitoJUnitRunner.class)
@ContextConfiguration(classes = { TestApp.class })
class MyTestClass{
..
}
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With