Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java decouple database manipulation code & make it testable

I want to write a test for the following method:

public void addItem(Item item) {

    items.add(0, item);
    DatabaseHelper.getInstance().writeOneItem(item);
}

The class is called ItemManager and it's responsibility is to manage Items that the user can save to or remove from a list. It should be kept in sync with an Sqlite database that persists the items on the list.

When the DatabaseHelper (ormlite) is not inited with init(Context context) (which is usually sone when my Andoid app starts, but is not done in my test), it's getInstance() method will return null and the method execution from above will crash.

What should I do here? I could just call init(Context context) from my test or check if DatabaseManager.getInstance() is null before calling anything on it. But that seems more like a workaround. It seems to me like I shouldn't do any Database stuff in this method and try to seperate the ItemManager from the Database as much as possible.

Any ideas on how the ideal solution would look, not in form of a concrete implementation but from a gerneral design standpoint?

I'm new to unit testing and having a hard time decoupling stuff from each other.

like image 438
fweigl Avatar asked Dec 25 '22 06:12

fweigl


2 Answers

In my opinion, your class ItemManager has to call the DatabaseHelper to write the item but your unit test just want to make sure that it does. You don't want to test that the DatabaseHelper actually writes the item in the database, that would be another test.

I would modify the design of your class: the DatabaseHelper.getInstance() should not be done directly in the method. Your ItemManager should have a private field with the instance of the DatabaseHelper. That way you can mock it and verify that it is called.

Using Mockito for example:

public void addItem(Item item) {
    items.add(0, item);
    this.databaseHelper.writeOneItem(item);
}

@Test
public void my_test() {
    // GIVEN
    DatabaseHelper databaseHelper = mock(DatbaseHelper.class);
    ItemManager manager = new ItemManager(databaseHelper);
    Item item = new Item()

    // WHEN
    manager.addItem(item);

    // THEN
    verify(databaseHelper).writeOneItem(item); // This verifies that the method writeOneItem of the "mock" is called with the "item" parameter
}

// Another test would check that the item is added to the "items" collection

Your unit test should be focused on testing ONE method and not the behaviour of the classes it uses.

In my example, I inject the DatabaseHelper in the ItemManager via constructor but you could use any method: constructor, setter, dependency injection framework, etc.

like image 116
Vincent Durmont Avatar answered Dec 28 '22 07:12

Vincent Durmont


Break the static dependance and use a mock framework (like mockito)

class ItemManager {
 ...
 // decoupling
 private DatabaseHelper instance = DatabaseHelper.getInstance();

 public void addItem(Item item) {
    items.add(0, item);
    instance.writeOneItem(item);
 }
}

With mockito :

class ItemManagerTest{
 // declare mock service
 @Mock
 DatabaseHelper instance;

 // inject mock service into your about to be tested class
 @InjectMocks
 ItemManager manager;

 @Test
 public void test() {
  // Given
  Item item = new Item();
  ...

  // When
  manager.addItem(item);

  // Then
  // assert that the service has been called with the right parameters
  verify(instance).writeOneItem(item);
 }
like image 25
Maxime ARNSTAMM Avatar answered Dec 28 '22 07:12

Maxime ARNSTAMM