Home > Software engineering >  Unit testing constructor initialization and public logic
Unit testing constructor initialization and public logic

Time:09-15

Suppose you have the following class:

public class RepositoryImpl implements Repository {
  private static final Object DEFAULT_OBJECT = new Object();

  private final Persistence persistence;
  private volatile Object cachedObject; // maybe ignore that this is volatile and non-final

  public RepositoryImpl(Persistence persistence) {
    this.persistence = persistence;
    this.cachedObject = getInitialCachedObject();
  }

  private Object getInitialCachedObject() {
    try {
      return persistence.get();
    } catch (ObjectNotFoundException e) {
      persistence.persist(DEFAULT_OBJECT);
      return DEFAULT_OBJECT;
    }
  }

  public Object update() { /*some logic*/ }
  public Object get() { /*some logic*/ }
  public Object delete() { /*some logic*/ }
}

Then I want to unit test the Repository class and I mock out the persistence. I want to have probably 2 tests that test the initialization logic (happy path and exception) and 3 more tests for the public methods.

The question is how should I approach the testing of this?

The possible options that I managed to think of are:

  1. Consider calling the initialization from outside through a public method after ctor
    • breaks the immutability (in my particular case this is already broken as cachedObject needs to be volatile and not final, but in the general case.. yeah)
  2. Create the RepositoryImpl in each test case instead of using @InjectMocks or @Before
  3. Create two nested test classes - one for the init logic and one for the core logic
  4. Somehow use @InjectMocks but re-initialize only in one test, not sure if possible
  5. Some lazy approach in get(), but also breaks immutability in the general case

To me option 3 seems clean, but maybe there is a better approach or further refactoring is needed idk. Any suggestions are highly appreciated.

Note that I cannot just use @Cachable in the Persistence because this is not a Spring project.

CodePudding user response:

  1. Create the RepositoryImpl in each test case instead of using @InjectMocks or @Before
  2. Create two nested test classes - one for the init logic and one for the core logic

I think that the options above are viable solutions, but you made a good point here:

  1. Somehow use @InjectMocks but re-initialize only in one test, not sure if possible

To achieve that you can simply ignore the instance stored in the test class field annotated with @InjectMocks and create a separate one, just in this test (as described in your second proposed approach).

@ExtendWith(MockitoExtension.class)
class RepositoryTest {

    @Mock
    Persistence persistence;
    @InjectMocks
    RepositoryImpl repository;

    @Test
    void oneOfMultipleTests() {
        // here you're defining the persistence field mock behavior
        // and using the repository field
    }

    @Test
    void objectNotFound() {
        var emptyPersistence = mock(Persistence.class);
        when(emptyPersistence.get()).thenThrow(...);

        var emptyBasedRepository = new RepositoryImpl(emptyPersistence);
        
        assertNotNull(emptyBasedRepository);
        verify(emptyPersistence).persist(argThat(...));
        // assert something else if you want
    }

}
  • Related