In episode 22 we how to seed a database with spring's @Sql annotation so lets extend it and find out how to test a domain with database interactions.
Detailed Video Notes
Getting started
For our exercise we have been given a user story to "Update an existing person's password" which will require us to create a Person table, POJO and a data access object. We will use spring's jdbcTemplate to interact with the database but it could be substituted by injecting mybatis or hibernate data access beans. Once those pieces are created we will put it together an integration test.
This process may vary depending what techniques and methodologies you are using such as TDD.
Project set up
[0:21]
Let's get started by creating a spring boot project from the starter web site selecting spring-boot-starter-jdbc
and spring-boot-starter-web
. We will then download and import the maven project into eclipse while adding derby as additional dependency in the pom.xml. Spring-boot recognizes derby is on the classpath and auto configures an embedded database which means you do not need to specify any connection URLs or driver class names (pretty neat). If you are wanting to dig into the specifics check out EmbeddedDatabaseConnection and EmbeddedDataSourceConfiguration.
<dependency>
<groupId>org.apache.derby</groupId>
<artifactId>derby</artifactId>
<version>10.11.1.1</version>
</dependency>
Derby could be substituted with other databases such as oracle, mysql or another database by specifying properties in application.properties or application.yml.
spring.datasource.url=jdbc:mysql://<hostname>[<:3306>]/<dbname>
spring.datasource.username=me
spring.datasource.password=me
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
Creating Person domain and DAO
[1:2]
Next lets create a Person
POJO and PersonDAO
. As we mentioned earlier the PersonDAO
will use JdbcTemplate
abstraction to access the database. We implemented a PersonRowMapper
that implements RowMapper
to map result set row to a Person object, a getPerson
and updatePassword
method.
public class Person {
private int personId;
private String firstName;
private String middleName;
private String lastName;
private String userId;
private String password;
}
@Component
public class PersonDAO {
@Autowired
private JdbcTemplate jdbcTemplate;
public class PersonRowMapper implements RowMapper<Person> {
@Override
public Person mapRow(ResultSet rs, int rowNum) throws SQLException {
Person person = new Person();
person.setPersonId(rs.getInt("PERSONID"));
person.setFirstName(rs.getString("firstname"));
person.setMiddleName(rs.getString("MIDDLENAME"));
person.setLastName(rs.getString("LASTNAME"));
person.setUserId(rs.getString("USERID"));
person.setPassword(rs.getString("PASSWORD"));
return person;
}
}
public Person getPerson(int personId) {
String sql = "SELECT * from PERSON WHERE PERSONID = " + personId;
return jdbcTemplate.queryForObject(sql, new PersonRowMapper());
}
public int updatePassword (Person person) {
String updateSql = "UPDATE PERSON SET PASSWORD = ? WHERE PERSONID = ?";
return jdbcTemplate.update(
updateSql,
new Object[]{person.getPassword(), person.getPersonId()},
new int[]{Types.VARCHAR, Types.INTEGER});
}
}
Setting up the database
[1:25]
Once the foundation is created, we will want to write a test to validate that a person's password was updated. An important thing to mention is when running integration tests against a database is that is in a known state before each test to have consistent reproducible tests. To achieve this we will use spring's @Sql and @SqlGroup annotation.
We covered this in detail in episode 22 so at a high level using these annotations we create a person table, preload the entry of "Fred" and drop the table upon completion of the test all against our embedded database.
...
@SqlGroup({
@Sql(executionPhase = ExecutionPhase.BEFORE_TEST_METHOD,
scripts = "classpath:beforeScripts.sql"),
@Sql(executionPhase = ExecutionPhase.AFTER_TEST_METHOD,
scripts = "classpath:afterScripts.sql") })
public class ApplicationTests {
...
Writing the test
[1:50]
After the database and data is set up we are ready to write our unit test. Since we know we seeded the database with a person and their id equaling one, we will create a Person
object setting the id to 1 and the password to "wilma". In the event that we didn't know which person existed we could first queried the database to get a random person and followed the same steps.
Next calling the personDAO.updatePassword
and passing the Person
object we just created will execute an update statement which we can see by examining the logs.
2015-01-25 07:47:20.585 DEBUG 49978 --- [ main] o.s.jdbc.core.JdbcTemplate : Executing prepared SQL statement [UPDATE PERSON SET PASSWORD = ? WHERE PERSONID = ?]
2015-01-25 07:47:20.585 DEBUG 49978 --- [ main] o.s.jdbc.datasource.DataSourceUtils : Fetching JDBC Connection from DataSource
2015-01-25 07:47:20.618 DEBUG 49978 --- [ main] o.s.jdbc.core.JdbcTemplate : SQL update affected 1 rows
Finally we can validate the update occured by calling personDAO.getPerson
.
2015-01-25 07:47:20.621 DEBUG 49978 --- [ main] o.s.jdbc.core.JdbcTemplate : Executing SQL query [SELECT * from PERSON WHERE PERSONID = 1]
Putting it all together
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = Application.class)
@WebAppConfiguration
@SqlGroup({
@Sql(executionPhase = ExecutionPhase.BEFORE_TEST_METHOD,
scripts = "classpath:beforeScripts.sql"),
@Sql(executionPhase = ExecutionPhase.AFTER_TEST_METHOD,
scripts = "classpath:afterScripts.sql") })
public class ApplicationTests {
@Autowired
private PersonDAO personDAO;
@Test
public void should_update_password() {
Person person = new Person();
person.setPersonId(1);
person.setPassword("wilma");
personDAO.updatePassword(person);
// validate update occurred
Person updatedPerson = personDAO.getPerson(1);
assertEquals("wilma", updatedPerson.getPassword());
}
}
Testing interactions between a domain and a database can be challenging and spring has made it easy to set up, tear down and execute scripts against a database. It is important to find the balance between what an integration test can offer and interactions you can test with mocking frameworks such as mockito.
Thanks for joining in today's level up, have a great day!