
About the usefulness of the TDD approach (
development through testing ,
test driven development ) did not hear only lazy or deaf. But today we will not discuss all its usefulness and beauty, as well as problems and disadvantages. Today we will try to see how to develop unit tests for spring applications. We will also touch on the manual management of transactions in unit tests.
A small note: sometimes spring application tests are not exactly unit tests, because we can raise and use very complex environments (DB, WebService, etc.) in them. Such tests are more
integration tests , but I think that now we will not raise philosophical questions.
To begin with, I propose to harmonize some terms and concepts:
- Unit-test - a test that checks the behavior of a small part of the application. This part can be a single class, one method, or a set of classes that implement some kind of architectural solution, and this solution should be tested for operability. For details, go here or there .
- Application context config - a configuration file in xml format for describing the structure of the spring application. About spring read here or there .
- DAO - data access object or data access object. The main purpose of this design pattern is to link the database and our application together. For details go here or there .
- A transaction is a group of sequential operations, which is a logical unit of work with data. A transaction can be executed either completely and successfully, respecting the integrity of the data and independently of other transactions running in parallel, or not executed at all and then it should have no effect. Read about transactions here or there .
All other terms and concepts are standard and have long been established or their description is uncritical here. For example, such a thing as IoC (
inversion of control ,
inversion of control ).
')
I completely forgot, I’m not writing the first about writing unit tests for spring applications, I’m a little
here and
there .
So, we have a
goal: to test the behavior of the class in the spring-application, in addition, you must manually manage transactions . To do this, we will create a simple spring application and write a unit test. Our unit-test at startup will initialize the application context config of our spring application and then call methods on the class we are testing. We will also develop a separate test in which we will manage transactions manually.
To begin,
create an application that we will test.
Technologies in the application will be as follows:
- Tool assembly and compilation - Apache Maven .
- Database - HSQLDB .
- The tool for mapping classes to the database (Object relation mapping) is Hibernate .
- Tool for configuring the application - Spring framework .
- Tool for creating unit tests - JUnit .
The final
file structure in the application will look like this:

By the way, you can
download it yourself (from SVN) and
view the source code of the running application
in the browser .
Let's start?
First , we need to create pom.xml in which we describe the build and compilation of the application (read about maven
here or
there ). In this
configuration file, we prescribe all dependencies on the libraries we use. Also at this step, we will create all the directories of our application.
Second , we will create a java persistente entity class -
ru.intr13.example.springTransactionalTest.Data . This class will describe the data model and with the help of the hibernate library it will be displayed in the database (a table will be automatically created in the database). Also in this step, we will create the
hibernate.cfg.xml file, where we will link to the class we developed.
Third , we will create an interface -
ru.intr13.example.springTransactionalTest.DataDao . In which we describe the main methods for working with our database. The checkpoint and shutdown methods are designed to work with hsqldb and their presence is connected with the peculiarity of the operation of this database (see details
here ).
Fourth , we will create an implementation of the interface we developed -
en.intr13.example.springTransactionalTest.DataHibernateDao . Where we implement all the methods described in the DataDao interface. It is worth noting that this class is inherited from the class org.springframework.orm.hibernate3.support.HibernateDaoSupport, which in turn is part of the Spring Dao library and already has methods for convenient work with the database.
Fifth , we will create an
application context config file to configure our application. In which we describe:
- Data source ( dataSource ), in which we describe the parameters for connecting to our hsqldb database.
- Factory for working with connections to the database and for displaying our model in the database ( sessionFactory ). When configuring the factory, we will provide a link to the hibernate.cfg.xml file, where all classes of our model are described. We will also write the parameters for creating and working with the database, a link to the data source.
- Service developed by us ( dataDao ). When configuring, we will specify a link to the sessionFactory .
- Transaction Manager ( transactionManager ). When configured, we specify a link to the sessionFactory and also indicate: what methods we need to start a new transaction.
In the sixth , we will create a
test application that initializes the application context config and works a bit with the service we developed. The results of the work are saved in our local database, which can be observed in the
data / test.db.script file (this file contains the data of our database):
CREATE SCHEMA PUBLIC AUTHORIZATION DBA
CREATE MEMORY TABLE DATA (ID BIGINT NOT NULL PRIMARY KEY ,TEXT VARCHAR (255))
CREATE MEMORY TABLE HIBERNATE_SEQUENCES(SEQUENCE_NAME VARCHAR (255),SEQUENCE_NEXT_HI_VALUE INTEGER )
CREATE USER SA PASSWORD ""
GRANT DBA TO SA
SET WRITE_DELAY 10
SET SCHEMA PUBLIC
INSERT INTO DATA VALUES (1, 'one' )
INSERT INTO DATA VALUES (2, 'two' )
INSERT INTO DATA VALUES (3, 'three' )
INSERT INTO HIBERNATE_SEQUENCES VALUES ( 'Data' ,1)
So, the
test application has been created and now we need to develop a unit test . To do this, we create a class
ru.intr13.example.springTransactionalTest.DataDaoTest . This class is derived from the org.springframework.test.context.junit4.AbstractTransactionalJUnit4SpringContextTests class, which allows you to run the application context config when running tests. This is done using annotations for the test class:
@ContextConfiguration(locations = { "/applicationContext.xml" }).
But for full testing, we need our test to have the opportunity to get a service developed by us. To do this, we register in our test field with reference to the service and set the annotation for this field - @Autowired:
@Autowired
private DataDao dataDao;
Now when you run the tests in this field will be set a link to the previously developed service.
And in the end it remains to write the text of the unit test:
@Test
public void simpleTest() {
String text = UUID.randomUUID().toString();
dataDao.save( new Data(text));
Collection result = dataDao.find(text);
Assert.assertEquals(1, result.size());
Assert.assertEquals(text, result.iterator().next().getText());
}
This test creates a Data object, stores it in a database, and then searches for a Data object by its contents.
I think there is nothing particularly difficult in the above, and this can even be used, but sometimes in such tests it is required to organize
manual management of transactions (that is, declaratively roll back or save the transaction, start a new transaction). How to do this is described
here , but now we consider a small example.
To manually manage transactions in tests, we need to get the transaction manager described in the application context config (transactionManager), which is done by creating a field in our test:
@Autowired
private PlatformTransactionManager transactionManager;
Next, we simply create a new transaction:
TransactionStatus transaction = transactionManager.getTransaction( new DefaultTransactionDefinition(TransactionDefinition.PROPAGATION_REQUIRES_NEW));
and then we commit to it (save):
transactionManager.commit(transaction);
or rollback:
transactionManager.rollback(transaction);
Knowing all of the above, we can write the following test, which creates several transactions manually:
@Test
public void comlexTest() {
TransactionStatus transaction = transactionManager.getTransaction( new DefaultTransactionDefinition(TransactionDefinition.PROPAGATION_REQUIRES_NEW));
String text = UUID.randomUUID().toString();
dataDao.save( new Data(text));
transactionManager.commit(transaction);
transaction = transactionManager.getTransaction( new DefaultTransactionDefinition(TransactionDefinition.PROPAGATION_REQUIRES_NEW));
Collection result = dataDao.find(text);
Assert.assertEquals(1, result.size());
Assert.assertEquals(text, result.iterator().next().getText());
transactionManager.rollback(transaction);
}
The final version of the test can be viewed
here .
Total: we created a test application based on the spring framework (the source code lies
here ). A way of testing individual services in the spring framework was demonstrated. It also showed how to manually manage transactions in unit tests. As a result, we saw that creating simple unit tests for the spring framework is a fairly simple task. The question of the appropriateness and the need to develop such tests was not considered, this is a topic for a separate conversation.
p / s
This is an edited version of a
post from my personal
blog . Do not count for advertising :)
p / s / s
Picture found
here . By the way, an attentive person will notice one funny thing :)