Having a component that represents the database is wonderful! You can use it from the business logic to read and write data. But should that component be unit-tested?
I would argue that it should not. Unit-tests are all about testing the contract of a component with its external actors.
Let's take a typical interface of a database component
void insertUsers(List<UserRecord> users);
List<UserRecord> fetchUsers(List<UserID> userIds);
void deleteUsers(List<UserID> userIds);
Each method listed utilizes the database library to send the appropriate queries to the database.
The external actors of the database component are:
- The business logic
- The database
Unit testing would require me to mock the connection, prepared statement and result set objects of the database library.
However since the entire database component consists only of interactions with the database library, any test I write will end up mirroring the component code and is hence fragile.
Testing the contract means asserting on the following:
- Calling insertUsers should write those users in the database
- Calling fetchUsers should retrieve those users from the database
- Calling deleteUsers should delete those users from the database
Testing interaction with the database library will lead to fragile code:
- A lot of equivalent SQL statements can be created. Changing a statement to an equivalent one does not break the contract and should not break the test
- One could use the database library in different ways: eg: use a statement instead of a prepared statement object. That again does not break the contract
- Changing the order of columns in a select or insert statement would yield equivalent results. No point in asserting on resultset.getString(0) or preparedStatement.setString(1, "Bill")
- The database library used should not matter.
The following thoughts, have lead me to the conclusion that unit-tests for the database component offer little value. I truly feel that an integration test which requires an actual database is the way to go.
Please share your thoughts on the subject; Could it be that I am missing something?
Edit: Please suggest how can the following code be unit-tested in a non-fragile way. Feel free to refactor the code if you like.
void insertUsers(List<UserRecord> users) throws RepositoryException{
try(Connection connection = datasource.getConnection();
PreparedStatement stmt = connection.prepareStatement("insert into users (Name, Surname, DateOfBirth) values (?,?,?)")){
for (UserRecord user : users){
stmt.setString(1, user.name);
stmt.setString(2, user.surname);
stmt.setTimestamp(3, user.dateOfBirth);
stmt.execute();
}
} catch(SQLException ex){
throw new RepositoryException(ex);
}
}