This recipe is an example of how to inject Spring managed beans into integration test classes. Even for IT tests, whose first objective is to assess the backend as a blackbox, it is sometimes necessary to reach out technical objects from the intermediate layer.
We will see how to reuse an instance of a Spring managed datasource
to be injected in our test class. This datasource
will help us to build an instance of jdbcTemplate
. From this jdbcTemplate
, we will query the database and simulate/validate processes that couldn't be tested otherwise.
@Autowired
a dataSource
SpringBean in our UserControllerIT
test. This bean is defined in the test-specific Spring configuration file (spring-context-api-test.xml
) resources
directory (cloudstreetmarket-api
):<context:property-placeholderlocation=" file:${user.home}/app/cloudstreetmarket.properties""/> <bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource" destroy-method="close""> <property name="driverClassName""> <value>com.mysql.jdbc.Driver</value> </property> <property name="url""> <value>${db.connection.url}</value> </property> <property name="username""> <value>${db.user.name}</value> </property> <property name="password""> <value>${db.user.passsword}</value> </property> <property name="defaultReadOnly"> <value>false</value> </property> </bean>
A jdbcTemplate
instance is created in the UserControllerIT
class from the @Autowired dataSource
bean:
@Autowired private JdbcTemplate jdbcTemplate; @Autowired public void setDataSource(DataSource dataSource) { this.jdbcTemplate = new JdbcTemplate(dataSource); }
jdbcTemplate
to insert and delete Social Connections
directly in the database (see Chapter 5, Authenticating with Spring MVCA). This allows us to bypass and simulate a successful user OAuth2 authentication flow (that normally happens through the web browser).For deleting social connections, we have created the following private method that is called as needed by the test(s:):
private void deleteConnection(String spi, String id) { this.jdbcTemplate.update("delete from userconnection where providerUserId = ? and userId = "?", new Object[] {spi, id}); }
UserControllerIT
class, the following two annotations can be noticed:@RunWith(SpringJUnit4ClassRunner.class)
tells JUnit to run with a custom extension of JUnit (SpringJUnit4ClassRunner
) that supports the Spring TestContext
Framework.@ContextConfiguration("classpath:spring-context-api-test.xml")
specifies where and how to load and configure the Spring application context:@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("classpath:spring-context-api-test.xml"") public class UserControllerIT extends AbstractCommonTestUser{ private static User userA; private static User userB; ... }
In its design, the
SpringJUnit4ClassRunner
is a direct subclass of the JUnit's BlockJUnit4ClassRunner
. SpringJUnit4ClassRunner
that initializes when a TestContextManager
is loaded. A TestContextManager
manages the life cycle of a TestContext
and can also reflect test events to the registered TestExecutionListeners
(from @BeforeClass
, @AfterClass
, @Before
, and @After
annotations).
By loading a Spring context, the SpringJUnit4ClassRunner
Spring context, SpringJUnit4ClassRunner
enables the possibility use Spring managed beans in test classes. The SpringJUnit4ClassRunner
also supports a set of annotations (either from JUnit or from Spring test) that can be used in test classes. The use of these annotations can be trusted for subsequently providing suitable life cycle management to context-defined objects.
Those annotations are @Test
(with its expected
and timeout
annotation parameters), @Timed
, @Repeat
, @Ignore
, @ProfileValueSourceConfiguration
, and @IfProfileValue
.
This class-level annotation is specific to Spring Test. It defines how and where to load a Spring Context for the test class.
Our definition in the recipe targets a specific Spring XML configuration file @ContextConfiguration("classpath:spring-context-api-test.xml").
However, since Spring 3.1 the contexts can be defined programmatically, @ContextConfiguration
can also target configuration classes as follows:
@ContextConfiguration(classes={AnnotationConfig.class,
WebSocketConfig.class})
As shown in the following snippet, both declaration types can be combined in the same annotation:
@ContextConfiguration(classes={AnnotationConfig.class,
WebSocketConfig.class}, locations={
"classpath:spring-context-api-test.xml
"})
We will see more in this section about the Spring JdbcTemplate that has been used for test purposes.
In Chapter1, Setup Routine for an Enterprise Spring Application, we have introduced the different modules that make the Spring Framework what it is today. One group of modules is Data Access and Integration. This group contains the JDBC, ORM, OXM, JMS, and transactions modules.
The JdbcTemplate
is a key-class part of the Spring JDBC core package. It reliably allows performing of database operations with straightforward utility methods and also provides an abstraction for big chunks of boilerplate code. Once more, this tool saves us time and offers patterns to design quality products.
Let's consider as an example the method in our test class that deletes connections:
jdbcTemplate.update("delete from userconnection where providerUserId = ? and userId = "?", new Object[] {spi, id});
Using jdbcTemplate
, deleting a database element is a one-line instruction. It creates a PreparedStatement
under the hood, chooses the right Type, depending upon the arguments we actually pass as values, and it manages the database connection for us, making sure to close this connection whatever happens.
The jdbcTemplate.update
method has been designed to issue a single SQL update operation. It can be used for inserts, updates, and also deletes.
As often in Spring, jdbcTemplate
also transforms the produced checked exceptions (if any) into unchecked exceptions. Here, the potential SQLExceptions
would be wrapped in a RuntimeException
.
The
jdbcTemplate.update
method also offers other argument Types:
jdbcTemplate.update(final PreparedStatementCreator psc, final KeyHolder generatedKeyHolder);
In the case of an insert, this method can be called when needed to read and potentially reuse the generated ID (which is unknown before the query execution).
In our example, if we would have wanted to reuse the generated connection IDs when inserting new connections, we would have done it as follows:
KeyHolder keyHolder = new GeneratedKeyHolder(); jdbcTemplate.update( new PreparedStatementCreator() { public PreparedStatement createPreparedStatement(Connection connection) throws SQLException { PreparedStatement ps = connection.prepareStatement("insert into userconnection (accessToken, ... , secret, userId ) values (?, ?, ... , ?, ?)", new String[] {"id""}); ps.setString(1, generateGuid()); ps.setDate(2, new Date(System.currentTimeMillis())); ... return ps; } }, keyHolder); Long Id = keyHolder.getKey().longValue();