TLDR
Introducing ResourceBundle class to represent the set of resources that a player has. Updating business logic and JSON serialization to leverage new ResourceBundle. Refactoring RollResult logic to be more testable. Adding unit test coverage to existing code / logic.

Mocking static method calls.
public class CatanGameTest {
// mock calls to the random number generator
MockedStatic<RollResult> mockedRollResult;
// TODO not needed for most tests
@BeforeEach
public void beforeEach() {
mockedRollResult = Mockito.mockStatic(RollResult.class);
mockedRollResult.when(RollResult::getRandomNumber)
.thenReturn(3)
.thenReturn(3)
.thenReturn(2)
.thenReturn(2)
.thenReturn(1)
.thenReturn(1);
}
@AfterEach
public void afterEach() {
mockedRollResult.close();
}
.
.
.
}
Main work
- Define the
ResourceBundleclass and itsadd()andincrement()instance methods. - Updating the
Playermodel’sresourcefield to use the newResourceBundletype.- This required use of Jackson
@JsonUnwrappedannotation.
- This required use of Jackson
- Finding a way to mock the random roll logic for unit testing.
- Ultimately landed on the
MockedStatic<T>class from Mockito.
- Ultimately landed on the
Challenges
- Having to mock static methods always feels awkwards and begs the question: “Is this designed poorly?”.
- Not all unit tests within the suite actually need the
@BeforeEachlogic to run, either.
- Not all unit tests within the suite actually need the
- Now at the point where making changes will be safer because of test coverage, but will require test updates / compilation fixes.
Learnings
- Adding a
lombok.configfile withlombok.addLombokGeneratedAnnotation = truewill mark all Lombok code with@Generatedannoation.- This allows Jacoco to ignore getter / setter / etc. code from Lombok when it computes code coverage percentages.
@JsonUnwrappedwas very nice for resolving the nested JSON created by my new data structure.- e.g.,
player.resourceslooked like this before the annoation:{ "resources": { "resourceBundle": { "0": 0, "1": 0, "2": 0, "3": 0, "4": 0 } } }
- e.g.,
- Must have been tired, because I was confused about how my
ResourceBundleclass could access theprivateinstance variables of otherResourceBundleinstances. That is just default behavior ofprivate, no need forprotected.- e.g., look at the
add(ResourceBundle other)method. other’s private instance variables are still accessible.
- e.g., look at the