patternjavaMinor
Unit test for a method that adds tweets to a database
Viewed 0 times
methoddatabasethatfortestaddstweetsunit
Problem
I have a class which inserts a list of tweets to a database table and also inserts the tweet's key with the associated filter's key to a many-to-many table. I am testing the sole public method of this class:
```
class TweetByFilterDatabaseRepository {
private final DatabaseFacade database;
private final Mapper toTweetContentValuesMapper;
private final TwoToOneMapper
toTweetAndSearchFilterContentValuesMapper;
private final Mapper toTweetMapper;
TweetByFilterDatabaseRepository(DatabaseFacade database,
Mapper toTweetContentValuesMapper,
TwoToOneMapper
toTweetAndSearchFilterContentValuesMapper,
Mapper toTweetMapper) {
this.database = database;
this.toTweetContentValuesMapper = toTweetContentValuesMapper;
this.toTweetAndSearchFilterContentValuesMapper = toTweetAndSearchFilterContentValuesMapper;
this.toTweetMapper = toTweetMapper;
}
public void addAll(SearchFilterEntity filter, List tweets) {
database.beginTransaction();
try {
for (TweetEntity tweet : tweets) {
try {
saveTweet(tweet);
saveTweetSearchFilterForeignKeys(filter, tweet);
} catch (MappingException e) {
//no-op
}
}
database.setTransactionSuccessful();
} finally {
database.endTransaction();
}
}
private void saveTweet(TweetEntity tweet) throws MappingException {
final ContentValuesWrapper tweetContentValues = toTweetContentValuesMapper.mapOrThrow(tweet);
database.insertOrUpdate(TweetSchema.TABLE_NAME, tweetContentValues, TweetSchema.COLUMN_ID);
}
private void saveTweetSearchFilterForeignKeys(SearchFilterEntity filter,
```
class TweetByFilterDatabaseRepository {
private final DatabaseFacade database;
private final Mapper toTweetContentValuesMapper;
private final TwoToOneMapper
toTweetAndSearchFilterContentValuesMapper;
private final Mapper toTweetMapper;
TweetByFilterDatabaseRepository(DatabaseFacade database,
Mapper toTweetContentValuesMapper,
TwoToOneMapper
toTweetAndSearchFilterContentValuesMapper,
Mapper toTweetMapper) {
this.database = database;
this.toTweetContentValuesMapper = toTweetContentValuesMapper;
this.toTweetAndSearchFilterContentValuesMapper = toTweetAndSearchFilterContentValuesMapper;
this.toTweetMapper = toTweetMapper;
}
public void addAll(SearchFilterEntity filter, List tweets) {
database.beginTransaction();
try {
for (TweetEntity tweet : tweets) {
try {
saveTweet(tweet);
saveTweetSearchFilterForeignKeys(filter, tweet);
} catch (MappingException e) {
//no-op
}
}
database.setTransactionSuccessful();
} finally {
database.endTransaction();
}
}
private void saveTweet(TweetEntity tweet) throws MappingException {
final ContentValuesWrapper tweetContentValues = toTweetContentValuesMapper.mapOrThrow(tweet);
database.insertOrUpdate(TweetSchema.TABLE_NAME, tweetContentValues, TweetSchema.COLUMN_ID);
}
private void saveTweetSearchFilterForeignKeys(SearchFilterEntity filter,
Solution
Disclaimer: I'm not a huge fan of mocks.
Some issues I see with the approach:
I suggest to take a different approach: use a fake database.
With an in-memory implementation of
the test class could become a lot simpler, easier to read,
and eliminate the current test smells.
I mentioned at the top that I don't like mocks.
But notice that I didn't eliminate all the mocks.
I only eliminated the ones that didn't help reducing complexity.
I kept the ones that are useful, and I have no problems with those.
It should be straightforward to add further tests for the cases of some tweets with mapping errors, all tweets with mapping errors, filtering logic, and so on.
Some issues I see with the approach:
- The fixture setup is quite complicated, involving a lot of mocks. It's a lot to read.
- Using loops in test methods is a test smell.
- Verifying that
.mapOrThrowgets called on each tweet, and verifying thatinsertOrUpdategets called with specific parameters ondatabasecall for concern. The test knows too much about intimate details of the implementation. As such, the tests are too tightly coupled to the implementation, and very likely fragile tests (another test smell). The slightest change in implementation details will likely break tests, and become a maintenance nightmare.
I suggest to take a different approach: use a fake database.
With an in-memory implementation of
DatabaseFacade,the test class could become a lot simpler, easier to read,
and eliminate the current test smells.
//@RunWith(MockitoJUnitRunner.class) -> not needed anymore -> to delete
public class TweetByFilterDatabaseRepositoryTest {
@Test
public void addingAllItems_withoutErrors_addsTweetsToDatabase() {
DatabaseFacade database = newFakeDatabase();
SearchFilterEntity filter = mock(SearchFilterEntity.class);
List tweets = mockedListOf(TweetEntity.class);
tweetByFilterDatabaseRepository.addAll(filter, tweets);
assertEquals(tweets, database.getTweets());
}
}I mentioned at the top that I don't like mocks.
But notice that I didn't eliminate all the mocks.
I only eliminated the ones that didn't help reducing complexity.
I kept the ones that are useful, and I have no problems with those.
It should be straightforward to add further tests for the cases of some tweets with mapping errors, all tweets with mapping errors, filtering logic, and so on.
Code Snippets
//@RunWith(MockitoJUnitRunner.class) -> not needed anymore -> to delete
public class TweetByFilterDatabaseRepositoryTest {
@Test
public void addingAllItems_withoutErrors_addsTweetsToDatabase() {
DatabaseFacade database = newFakeDatabase();
SearchFilterEntity filter = mock(SearchFilterEntity.class);
List<TweetEntity> tweets = mockedListOf(TweetEntity.class);
tweetByFilterDatabaseRepository.addAll(filter, tweets);
assertEquals(tweets, database.getTweets());
}
}Context
StackExchange Code Review Q#128287, answer score: 2
Revisions (0)
No revisions yet.