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:
- 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)
- Create the RepositoryImpl in each test case instead of using @InjectMocks or @Before
- Create two nested test classes - one for the init logic and one for the core logic
- Somehow use @InjectMocks but re-initialize only in one test, not sure if possible
- 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:
- Create the RepositoryImpl in each test case instead of using @InjectMocks or @Before
- 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:
- 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
}
}