Building Mancala Game in Microservices Using Spring Boot (Part 4: Testing)
The final piece in the puzzle.
Join the DZone community and get the full member experience.
Join For FreeIn the first article "Building Mancala Game in Microservices Using Spring Boot (Solution Architecture)", I explained the overall architecture of the solution taken for implementing the Mancala game using the Microservices approach.
In part2 (Building Mancala Game in Microservices Using Spring Boot (Mancala API Implementation), I have discussed the detail implementation of 'mancala-api' microservice.
In part 3 (Building Mancala Game in Microservices Using Spring Boot (Web Client Microservice Implementation), I have provided the implementation details for 'mancala-web' microservice which is the only client for 'mancala-api' service in our example.
Now in this article, I am going to discuss the importance of TDD (Test Driven Development) approach and developing various Tests frameworks for our Mancala Game implementation.
The complete source code for this article is available in my GitHub.
To build and run this application, please follow the instructions I have provided here.
1 — TDD and Its Importance
Nowadays, designing and developing a robust API for services is crucial knowing that when we are using Microservices architecture we might be dealing with hundreds or in some cases thousands of microservices all are interacting with each other through their exposed APIs or Events rather than old monolith inter-process communication mechanism. Therefore, there is a need to design better robust APIs while having a TDD approach in mind.
There are various tools and frameworks out there helping developers to design better APIs for their microservices such as Swagger.io, ApiBuilder.io, Apicuri.io, etc. However, using TDD (Test Driven Development) methodology in developing robust APIs and its impact on delivering production-ready APIs is inevitable to everyone in this field. Therefore, with the increase of Microservices popularity, TDD is one of those methodologies which is dominant amongst most IT companies because of its great impact on building such robust APIs for microservices.
In this article, I have written I have covered all aspects of Testing for our both microservices, 'mancala-api' and 'mancala-web' in-depth so you might be able to apply them similarly into your own projects.
2 — Unit Testing Using Spring Boot Test
Spring Boot provides a number of utilities and annotations to help when testing your application.
Test support is provided by two modules: spring-boot-test
contains core items, and spring-boot-test-autoconfigure
supports auto-configuration for tests.
I have used the spring-boot-starter-test
“Starter”, which imports both Spring Boot test modules as well as JUnit, AssertJ, Hamcrest, and a number of other useful libraries.
When we are doing TDD, we have two approaches to take:
One is inside-out testing.
The other one is outside-in testing.
I have chosen the outside-in approach in my implementation. In outside-in TDD we work our way from the outside of the system to the inside, only implementing what is required to serve the outside.
3 — API Design
While designing the Mancala Game API based on SOLID principles, I ended up with below interfaces and classes for 'mancala-api' microservice:
Model Classes
KalahaGame class
This class holds complete information about one Mancala Game including id, pits, and player turn:
@Document(collection = "games")
@Setter
@Getter
@AllArgsConstructor
@NoArgsConstructor
public class KalahaGame implements Serializable{
@Id
private String id;
private List<KalahaPit> pits;
private PlayerTurns playerTurn;
public KalahaGame(int pitStones)
{
// implementation goes here
}
}
KalahaPit class
This class represents one Pit within Mancala Game:
@AllArgsConstructor
@NoArgsConstructor
@Data
public class KalahaPit implements Serializable {
private Integer id;
private Integer stones;
@Override
public String toString() {
return id.toString() +
":" +
stones.toString() ;
}
}
KalahaHouse class
This class is a subclass of KalahaPit representing one Kalaha House:
public class KalahaHouse extends KalahaPit {
public KalahaHouse(Integer id) {
super(id , 0);
}
}
API Interfaces
KalahaGameApi
public interface KalahaGameApi {
KalahaGame createGame(int stones);
}
KalahaSowingApi
public interface KalahaSowingApi {
KalahaGame sow (KalahaGame game, int pitIndex);
}
API Implementation Classes
MancalaGameService
This class provides an implementation for KalahaGameApi interface:
@Service
public class KalahaGameService implements KalahaGameApi {
@Autowired
private KalahaGameRepository kalahaGameRepository;
@Override
public KalahaGame createGame(int pitStones) {
KalahaGame kalahaGame = new KalahaGame(pitStones);
// implementation goes here
return kalahaGame;
}
}
MancalaSowingService
This class provides an implementation for KalahaSowingApi interface:
@Service
public class MancalaSowingService implements KalahaSowingApi {
// This method perform sowing the game on specific pit index
@Override
public KalahaGame sow(KalahaGame game, int requestedPitId) {
// implementation goes here
return game;
}
}
Repositories Interfaces
KalahaGameRepository
This interface is a Spring Boot implementation of MongoDB repository responsible for CRUD operations of KalahaGame instances:
public interface KalahaGameRepository extends MongoRepository<KalahaGame, String> {
}
Controller Classes
MancalaController
This class provides the Endpoint for clients accessing Mancala API implementation:
@Slf4j
@RestController
@RequestMapping("/games")
public class MancalaController {
@Autowired
private KalahaGameService mancalaGameService;
@Autowired
private MancalaSowingService mancalaSowingService;
@Value("${mancala.pit.stones}")
private Integer pitStones;
@PostMapping
public ResponseEntity<KalahaGame> createGame() throws Exception {
KalahaGame game = mancalaGameService.createGame(pitStones);
// the rest of implementation goes here
return ResponseEntity.ok(game);
}
@PutMapping(value = "{gameId}/pits/{pitId}")
public ResponseEntity<KalahaGame> sowGame(
@PathVariable(value = "gameId") String gameId,
@PathVariable(value = "pitId") Integer pitId) throws Exception {
KalahaGame kalahaGame = mancalaGameService.loadGame(gameId);
kalahaGame = mancalaSowingService.sow(kalahaGame, pitId);
// the rest of implementation goes here
return ResponseEntity.ok(kalahaGame);
}
}
4 — Testing Roadmap
I will start writing tests according to the below order:
Start testing Game implementation classes which ensures correct implementation of Mancala Game logic first.
Then I will move forward testing MongoDB Repositories, to check correct persistent operations for domain classes.
Then writing Business Services tests which will check the accuracy of Business services.
- Then, Controller tests which check if my controller provides a proper response to each client's requests.
Then, writing Integration Tests for 'mancala-web' microservice which is the only client for 'mancala-api' microservice in this example using Wiremock.
Finally, writing Consumer Driven Contract (CDC) tests to make sure that the API implementation for Mancala Game provided by 'mancala-api' microservice remains in sync with Mancala Web client based on defined contracts.
5 — Mancala Game Unit Tests
This class will be implementing the sowing logic for Mancala game and therefore we need to consider various situations where the game might behave differently based on the Mancala Game rules. I am using Mockito for these tests:
@RunWith(MockitoJUnitRunner.class)
@DirtiesContext
public class MancalaSowingServiceTests {
private static final Integer defaultPitStones = 6;
private KalahaGame game;
private MancalaSowingService mancalaSowingService;
@Before
public void setupTest (){
this.game = new KalahaGame(defaultPitStones);
this.mancalaSowingService = new MancalaSowingService();
}
@Test
public void testGameCreation () {
Assert.assertNotNull(this.game);
Assertions.assertThat(this.game.getPits().toString()).isEqualTo("[1:6, 2:6, 3:6, 4:6, 5:6, 6:6, 7:0, 8:6, 9:6, 10:6, 11:6, 12:6, 13:6, 14:0]");
}
@Test
public void testSowOfSecondPitPlayerA(){
Assertions.assertThat(this.game.getPits().toString()).isEqualTo("[1:6, 2:6, 3:6, 4:6, 5:6, 6:6, 7:0, 8:6, 9:6, 10:6, 11:6, 12:6, 13:6, 14:0]");
this.game = mancalaSowingService.sow(this.game, 2);
Assertions.assertThat(this.game.getPits().toString()).isEqualTo("[1:6, 2:0, 3:7, 4:7, 5:7, 6:7, 7:1, 8:7, 9:6, 10:6, 11:6, 12:6, 13:6, 14:0]");
Assertions.assertThat(this.game.getPlayerTurn().getTurn()).isEqualTo("B");
}
@Test
public void testSowOfSecondPitPlayerB(){
Assertions.assertThat(this.game.getPits().toString()).isEqualTo("[1:6, 2:6, 3:6, 4:6, 5:6, 6:6, 7:0, 8:6, 9:6, 10:6, 11:6, 12:6, 13:6, 14:0]");
this.game = mancalaSowingService.sow(this.game, 2);
Assertions.assertThat(this.game.getPits().toString()).isEqualTo("[1:6, 2:0, 3:7, 4:7, 5:7, 6:7, 7:1, 8:7, 9:6, 10:6, 11:6, 12:6, 13:6, 14:0]");
Assertions.assertThat(this.game.getPlayerTurn().getTurn()).isEqualTo("B");
this.game = mancalaSowingService.sow(this.game, 9);
Assertions.assertThat(this.game.getPits().toString()).isEqualTo("[1:7, 2:0, 3:7, 4:7, 5:7, 6:7, 7:1, 8:7, 9:0, 10:7, 11:7, 12:7, 13:7, 14:1]");
Assertions.assertThat(this.game.getPlayerTurn().getTurn()).isEqualTo("A");
}
@Test
public void testSowOfSixthPitPlayerA(){
Assertions.assertThat(this.game.getPits().toString()).isEqualTo("[1:6, 2:6, 3:6, 4:6, 5:6, 6:6, 7:0, 8:6, 9:6, 10:6, 11:6, 12:6, 13:6, 14:0]");
this.game = mancalaSowingService.sow(this.game, 2);
Assertions.assertThat(this.game.getPits().toString()).isEqualTo("[1:6, 2:0, 3:7, 4:7, 5:7, 6:7, 7:1, 8:7, 9:6, 10:6, 11:6, 12:6, 13:6, 14:0]");
Assertions.assertThat(this.game.getPlayerTurn().getTurn()).isEqualTo("B");
this.game = mancalaSowingService.sow(this.game, 9);
Assertions.assertThat(this.game.getPits().toString()).isEqualTo("[1:7, 2:0, 3:7, 4:7, 5:7, 6:7, 7:1, 8:7, 9:0, 10:7, 11:7, 12:7, 13:7, 14:1]");
Assertions.assertThat(this.game.getPlayerTurn().getTurn()).isEqualTo("A");
this.game = mancalaSowingService.sow(this.game, 6);
Assertions.assertThat(this.game.getPits().toString()).isEqualTo("[1:7, 2:0, 3:7, 4:7, 5:7, 6:0, 7:2, 8:8, 9:1, 10:8, 11:8, 12:8, 13:8, 14:1]");
Assertions.assertThat(this.game.getPlayerTurn().getTurn()).isEqualTo("B");
}
@Test
public void testWrongTurnByPlayerA(){
Assertions.assertThat(this.game.getPits().toString()).isEqualTo("[1:6, 2:6, 3:6, 4:6, 5:6, 6:6, 7:0, 8:6, 9:6, 10:6, 11:6, 12:6, 13:6, 14:0]");
this.game = mancalaSowingService.sow(this.game, 2);
Assertions.assertThat(this.game.getPits().toString()).isEqualTo("[1:6, 2:0, 3:7, 4:7, 5:7, 6:7, 7:1, 8:7, 9:6, 10:6, 11:6, 12:6, 13:6, 14:0]");
Assertions.assertThat(this.game.getPlayerTurn().getTurn()).isEqualTo("B");
this.game = mancalaSowingService.sow(this.game, 9);
Assertions.assertThat(this.game.getPits().toString()).isEqualTo("[1:7, 2:0, 3:7, 4:7, 5:7, 6:7, 7:1, 8:7, 9:0, 10:7, 11:7, 12:7, 13:7, 14:1]");
Assertions.assertThat(this.game.getPlayerTurn().getTurn()).isEqualTo("A");
this.game = mancalaSowingService.sow(this.game, 6);
Assertions.assertThat(this.game.getPits().toString()).isEqualTo("[1:7, 2:0, 3:7, 4:7, 5:7, 6:0, 7:2, 8:8, 9:1, 10:8, 11:8, 12:8, 13:8, 14:1]");
Assertions.assertThat(this.game.getPlayerTurn().getTurn()).isEqualTo("B");
// This is a wrong turn therefore the output remains the same as well as player turn
this.game = mancalaSowingService.sow(this.game, 1);
Assertions.assertThat(this.game.getPits().toString()).isEqualTo("[1:7, 2:0, 3:7, 4:7, 5:7, 6:0, 7:2, 8:8, 9:1, 10:8, 11:8, 12:8, 13:8, 14:1]");
Assertions.assertThat(this.game.getPlayerTurn().getTurn()).isEqualTo("B");
}
@Test
public void testSowOfSixthPitPlayerB(){
Assertions.assertThat(this.game.getPits().toString()).isEqualTo("[1:6, 2:6, 3:6, 4:6, 5:6, 6:6, 7:0, 8:6, 9:6, 10:6, 11:6, 12:6, 13:6, 14:0]");
this.game = mancalaSowingService.sow(this.game, 2);
Assertions.assertThat(this.game.getPits().toString()).isEqualTo("[1:6, 2:0, 3:7, 4:7, 5:7, 6:7, 7:1, 8:7, 9:6, 10:6, 11:6, 12:6, 13:6, 14:0]");
Assertions.assertThat(this.game.getPlayerTurn().getTurn()).isEqualTo("B");
this.game = mancalaSowingService.sow(this.game, 9);
Assertions.assertThat(this.game.getPits().toString()).isEqualTo("[1:7, 2:0, 3:7, 4:7, 5:7, 6:7, 7:1, 8:7, 9:0, 10:7, 11:7, 12:7, 13:7, 14:1]");
Assertions.assertThat(this.game.getPlayerTurn().getTurn()).isEqualTo("A");
this.game = mancalaSowingService.sow(this.game, 6);
Assertions.assertThat(this.game.getPits().toString()).isEqualTo("[1:7, 2:0, 3:7, 4:7, 5:7, 6:0, 7:2, 8:8, 9:1, 10:8, 11:8, 12:8, 13:8, 14:1]");
Assertions.assertThat(this.game.getPlayerTurn().getTurn()).isEqualTo("B");
this.game = mancalaSowingService.sow(this.game, 13);
Assertions.assertThat(this.game.getPits().toString()).isEqualTo("[1:8, 2:1, 3:8, 4:8, 5:8, 6:1, 7:2, 8:9, 9:1, 10:8, 11:8, 12:8, 13:0, 14:2]");
Assertions.assertThat(this.game.getPlayerTurn().getTurn()).isEqualTo("A");
}
@Test
public void testWrongTurnByPlayerB(){
Assertions.assertThat(this.game.getPits().toString()).isEqualTo("[1:6, 2:6, 3:6, 4:6, 5:6, 6:6, 7:0, 8:6, 9:6, 10:6, 11:6, 12:6, 13:6, 14:0]");
this.game = mancalaSowingService.sow(this.game, 2);
Assertions.assertThat(this.game.getPits().toString()).isEqualTo("[1:6, 2:0, 3:7, 4:7, 5:7, 6:7, 7:1, 8:7, 9:6, 10:6, 11:6, 12:6, 13:6, 14:0]");
Assertions.assertThat(this.game.getPlayerTurn().getTurn()).isEqualTo("B");
this.game = mancalaSowingService.sow(this.game, 9);
Assertions.assertThat(this.game.getPits().toString()).isEqualTo("[1:7, 2:0, 3:7, 4:7, 5:7, 6:7, 7:1, 8:7, 9:0, 10:7, 11:7, 12:7, 13:7, 14:1]");
Assertions.assertThat(this.game.getPlayerTurn().getTurn()).isEqualTo("A");
this.game = mancalaSowingService.sow(this.game, 6);
Assertions.assertThat(this.game.getPits().toString()).isEqualTo("[1:7, 2:0, 3:7, 4:7, 5:7, 6:0, 7:2, 8:8, 9:1, 10:8, 11:8, 12:8, 13:8, 14:1]");
Assertions.assertThat(this.game.getPlayerTurn().getTurn()).isEqualTo("B");
this.game = mancalaSowingService.sow(this.game, 13);
Assertions.assertThat(this.game.getPits().toString()).isEqualTo("[1:8, 2:1, 3:8, 4:8, 5:8, 6:1, 7:2, 8:9, 9:1, 10:8, 11:8, 12:8, 13:0, 14:2]");
Assertions.assertThat(this.game.getPlayerTurn().getTurn()).isEqualTo("A");
// This is a wrong turn therefore the output remains the same as well as player turn
this.game = mancalaSowingService.sow(this.game, 9);
Assertions.assertThat(this.game.getPits().toString()).isEqualTo("[1:8, 2:1, 3:8, 4:8, 5:8, 6:1, 7:2, 8:9, 9:1, 10:8, 11:8, 12:8, 13:0, 14:2]");
Assertions.assertThat(this.game.getPlayerTurn().getTurn()).isEqualTo("A");
}
@Test
public void testSowOfCollectingOppositePitStoneByPlayerA(){
Assertions.assertThat(this.game.getPits().toString()).isEqualTo("[1:6, 2:6, 3:6, 4:6, 5:6, 6:6, 7:0, 8:6, 9:6, 10:6, 11:6, 12:6, 13:6, 14:0]");
this.game = mancalaSowingService.sow(this.game, 2);
Assertions.assertThat(this.game.getPits().toString()).isEqualTo("[1:6, 2:0, 3:7, 4:7, 5:7, 6:7, 7:1, 8:7, 9:6, 10:6, 11:6, 12:6, 13:6, 14:0]");
Assertions.assertThat(this.game.getPlayerTurn().getTurn()).isEqualTo("B");
this.game = mancalaSowingService.sow(this.game, 9);
Assertions.assertThat(this.game.getPits().toString()).isEqualTo("[1:7, 2:0, 3:7, 4:7, 5:7, 6:7, 7:1, 8:7, 9:0, 10:7, 11:7, 12:7, 13:7, 14:1]");
Assertions.assertThat(this.game.getPlayerTurn().getTurn()).isEqualTo("A");
this.game = mancalaSowingService.sow(this.game, 6);
Assertions.assertThat(this.game.getPits().toString()).isEqualTo("[1:7, 2:0, 3:7, 4:7, 5:7, 6:0, 7:2, 8:8, 9:1, 10:8, 11:8, 12:8, 13:8, 14:1]");
Assertions.assertThat(this.game.getPlayerTurn().getTurn()).isEqualTo("B");
this.game = mancalaSowingService.sow(this.game, 13);
Assertions.assertThat(this.game.getPits().toString()).isEqualTo("[1:8, 2:1, 3:8, 4:8, 5:8, 6:1, 7:2, 8:9, 9:1, 10:8, 11:8, 12:8, 13:0, 14:2]");
Assertions.assertThat(this.game.getPlayerTurn().getTurn()).isEqualTo("A");
this.game = mancalaSowingService.sow(this.game, 3);
Assertions.assertThat(this.game.getPits().toString()).isEqualTo("[1:8, 2:1, 3:0, 4:9, 5:9, 6:2, 7:3, 8:10, 9:2, 10:9, 11:9, 12:8, 13:0, 14:2]");
Assertions.assertThat(this.game.getPlayerTurn().getTurn()).isEqualTo("B");
this.game = mancalaSowingService.sow(this.game, 9);
Assertions.assertThat(this.game.getPits().toString()).isEqualTo("[1:8, 2:1, 3:0, 4:9, 5:9, 6:2, 7:3, 8:10, 9:0, 10:10, 11:10, 12:8, 13:0, 14:2]");
Assertions.assertThat(this.game.getPlayerTurn().getTurn()).isEqualTo("A");
this.game = mancalaSowingService.sow(this.game, 2);
Assertions.assertThat(this.game.getPits().toString()).isEqualTo("[1:8, 2:0, 3:0, 4:9, 5:9, 6:2, 7:14, 8:10, 9:0, 10:10, 11:0, 12:8, 13:0, 14:2]");
Assertions.assertThat(this.game.getPlayerTurn().getTurn()).isEqualTo("B");
}
@Test
public void testSowOfCollectingOppositePitStoneByPlayerB(){
Assertions.assertThat(this.game.getPits().toString()).isEqualTo("[1:6, 2:6, 3:6, 4:6, 5:6, 6:6, 7:0, 8:6, 9:6, 10:6, 11:6, 12:6, 13:6, 14:0]");
this.game = mancalaSowingService.sow(this.game, 2);
Assertions.assertThat(this.game.getPits().toString()).isEqualTo("[1:6, 2:0, 3:7, 4:7, 5:7, 6:7, 7:1, 8:7, 9:6, 10:6, 11:6, 12:6, 13:6, 14:0]");
Assertions.assertThat(this.game.getPlayerTurn().getTurn()).isEqualTo("B");
this.game = mancalaSowingService.sow(this.game, 9);
Assertions.assertThat(this.game.getPits().toString()).isEqualTo("[1:7, 2:0, 3:7, 4:7, 5:7, 6:7, 7:1, 8:7, 9:0, 10:7, 11:7, 12:7, 13:7, 14:1]");
Assertions.assertThat(this.game.getPlayerTurn().getTurn()).isEqualTo("A");
this.game = mancalaSowingService.sow(this.game, 6);
Assertions.assertThat(this.game.getPits().toString()).isEqualTo("[1:7, 2:0, 3:7, 4:7, 5:7, 6:0, 7:2, 8:8, 9:1, 10:8, 11:8, 12:8, 13:8, 14:1]");
Assertions.assertThat(this.game.getPlayerTurn().getTurn()).isEqualTo("B");
this.game = mancalaSowingService.sow(this.game, 13);
Assertions.assertThat(this.game.getPits().toString()).isEqualTo("[1:8, 2:1, 3:8, 4:8, 5:8, 6:1, 7:2, 8:9, 9:1, 10:8, 11:8, 12:8, 13:0, 14:2]");
Assertions.assertThat(this.game.getPlayerTurn().getTurn()).isEqualTo("A");
this.game = mancalaSowingService.sow(this.game, 3);
Assertions.assertThat(this.game.getPits().toString()).isEqualTo("[1:8, 2:1, 3:0, 4:9, 5:9, 6:2, 7:3, 8:10, 9:2, 10:9, 11:9, 12:8, 13:0, 14:2]");
Assertions.assertThat(this.game.getPlayerTurn().getTurn()).isEqualTo("B");
this.game = mancalaSowingService.sow(this.game, 9);
Assertions.assertThat(this.game.getPits().toString()).isEqualTo("[1:8, 2:1, 3:0, 4:9, 5:9, 6:2, 7:3, 8:10, 9:0, 10:10, 11:10, 12:8, 13:0, 14:2]");
Assertions.assertThat(this.game.getPlayerTurn().getTurn()).isEqualTo("A");
this.game = mancalaSowingService.sow(this.game, 2);
Assertions.assertThat(this.game.getPits().toString()).isEqualTo("[1:8, 2:0, 3:0, 4:9, 5:9, 6:2, 7:14, 8:10, 9:0, 10:10, 11:0, 12:8, 13:0, 14:2]");
Assertions.assertThat(this.game.getPlayerTurn().getTurn()).isEqualTo("B");
this.game = mancalaSowingService.sow(this.game, 8);
Assertions.assertThat(this.game.getPits().toString()).isEqualTo("[1:9, 2:1, 3:1, 4:10, 5:9, 6:2, 7:14, 8:0, 9:1, 10:11, 11:1, 12:9, 13:1, 14:3]");
Assertions.assertThat(this.game.getPlayerTurn().getTurn()).isEqualTo("A");
this.game = mancalaSowingService.sow(this.game, 1);
Assertions.assertThat(this.game.getPits().toString()).isEqualTo("[1:0, 2:2, 3:2, 4:11, 5:10, 6:3, 7:15, 8:1, 9:2, 10:12, 11:1, 12:9, 13:1, 14:3]");
Assertions.assertThat(this.game.getPlayerTurn().getTurn()).isEqualTo("B");
this.game = mancalaSowingService.sow(this.game, 9);
Assertions.assertThat(this.game.getPits().toString()).isEqualTo("[1:0, 2:2, 3:2, 4:11, 5:10, 6:3, 7:15, 8:1, 9:0, 10:13, 11:2, 12:9, 13:1, 14:3]");
Assertions.assertThat(this.game.getPlayerTurn().getTurn()).isEqualTo("A");
this.game = mancalaSowingService.sow(this.game, 2);
Assertions.assertThat(this.game.getPits().toString()).isEqualTo("[1:0, 2:0, 3:3, 4:12, 5:10, 6:3, 7:15, 8:1, 9:0, 10:13, 11:2, 12:9, 13:1, 14:3]");
Assertions.assertThat(this.game.getPlayerTurn().getTurn()).isEqualTo("B");
this.game = mancalaSowingService.sow(this.game, 8);
Assertions.assertThat(this.game.getPits().toString()).isEqualTo("[1:0, 2:0, 3:3, 4:12, 5:0, 6:3, 7:15, 8:0, 9:0, 10:13, 11:2, 12:9, 13:1, 14:14]");
Assertions.assertThat(this.game.getPlayerTurn().getTurn()).isEqualTo("A");
}
}
MongoDB Repository Tests
I have used Embedded MongoDB to test MongoDB repository and related services precisely. Therefore, I have included below dependencies into pom.xml file:
<!-- Embedded MongoDB used for Tests -->
<dependency>
<groupId>de.flapdoodle.embed</groupId>
<artifactId>de.flapdoodle.embed.mongo</artifactId>
<scope>test</scope>
</dependency>
@DataMongoTest
@RunWith(SpringRunner.class)
@DirtiesContext
public class KalahaGameRepositoryTests {
@Autowired
KalahaGameRepository gameRepository;
@Test
public void testRepository() throws Exception {
this.gameRepository.deleteAll();
KalahaGame saved= this.gameRepository.save(new KalahaGame(6));
Assert.assertNotNull(saved.getId());
List<KalahaGame> list = this.gameRepository.findAll();
Assert.assertEquals(1, list.size());
}
}
KalahaGameService Tests
This class tests the functionality of KalahaGameService class which is responsible for managing KalahaGame instances with our MongoDB persistent storage as well as loading and storing them into Redis cache system. To run these tests we need to add an embedded Redis server to our project as described below.
Embedded Redis
Step 1: Adding below dependency to the project's pom.xml file:
<!-- Redis Embedded server -->
<dependency>
<groupId>com.github.kstyrc</groupId>
<artifactId>embedded-redis</artifactId>
<version>0.6</version>
</dependency>
Step 2: Adding below class to manage Redis server startup and shutdown operations into our test codebase:
@Component
public class EmbeddedRedis implements ExitCodeGenerator, DisposableBean{
@Value("${spring.redis.port}")
private int redisPort;
private RedisServer redisServer;
@PostConstruct
public void startRedis() throws IOException {
redisServer = RedisServer.builder()
.port(redisPort)
.setting("maxmemory 128M") //maxheap 128M
.build();
redisServer.start();
}
@PreDestroy
public void stopRedis() {
redisServer.stop();
}
@Override
public int getExitCode() {
redisServer.stop();
return 0;
}
@Override
public void destroy() throws Exception {
redisServer.stop();
}
}
The above code starts the Redis server on the component's initialization and shuts it down on the program exit event or component's destruction which together will enable us to perform all our unit tests against the KalahaGameService class.
KalahaGameServiceTests
Below are a few examples of unit tests we will need to create to make sure KalahaGameService operations are guaranteed to work properly. Perhaps you can enhance this class and add more tests to it:
package com.dzone.mancala.game.service;
import com.dzone.mancala.game.model.KalahaGame;
import com.dzone.mancala.game.model.KalahaPit;
import com.dzone.mancala.game.repository.KalahaGameRepository;
import org.assertj.core.api.BDDAssertions;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit4.SpringRunner;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
@SpringBootTest
@DirtiesContext (classMode = DirtiesContext.ClassMode.AFTER_CLASS)
@RunWith(SpringRunner.class)
public class KalahaGameServiceTests {
@MockBean
private KalahaGameRepository kalahaGameRepository;
@Autowired
private KalahaGameService kalahaGameService;
@Test
public void testCreatingNewlyGameInstance () throws Exception {
KalahaGame gameInstance = kalahaGameService.createGame(6);
BDDAssertions.then(gameInstance.getPlayerTurn()).isNull();
BDDAssertions.then (gameInstance.getPits()).size().isEqualTo(14);
BDDAssertions.then (gameInstance.getPit(1).getStones()).isEqualTo(6);
BDDAssertions.then (gameInstance.getPit(2).getStones()).isEqualTo(6);
BDDAssertions.then (gameInstance.getPit(3).getStones()).isEqualTo(6);
BDDAssertions.then (gameInstance.getPit(4).getStones()).isEqualTo(6);
BDDAssertions.then (gameInstance.getPit(5).getStones()).isEqualTo(6);
BDDAssertions.then (gameInstance.getPit(6).getStones()).isEqualTo(6);
BDDAssertions.then (gameInstance.getPit(7).getStones()).isEqualTo(0);
BDDAssertions.then (gameInstance.getPit(8).getStones()).isEqualTo(6);
BDDAssertions.then (gameInstance.getPit(9).getStones()).isEqualTo(6);
BDDAssertions.then (gameInstance.getPit(10).getStones()).isEqualTo(6);
BDDAssertions.then (gameInstance.getPit(11).getStones()).isEqualTo(6);
BDDAssertions.then (gameInstance.getPit(12).getStones()).isEqualTo(6);
BDDAssertions.then (gameInstance.getPit(13).getStones()).isEqualTo(6);
BDDAssertions.then (gameInstance.getPit(14).getStones()).isEqualTo(0);
}
@Test
public void testLoadingGameInstanceFromRepository () throws Exception {
KalahaGame expectedGame = new KalahaGame("5d414dcd24e4990006e7c9d3" , 6);
List<KalahaPit> kalahaPits = Arrays.asList(
new KalahaPit(1 , 6),
new KalahaPit(2 , 6),
new KalahaPit(3 , 6),
new KalahaPit(4 , 6),
new KalahaPit(5 , 6),
new KalahaPit(6 , 6),
new KalahaPit(7 , 0),
new KalahaPit(8 , 6),
new KalahaPit(9 , 6),
new KalahaPit(10 , 6),
new KalahaPit(11 , 6),
new KalahaPit(12 , 6),
new KalahaPit(13 , 6),
new KalahaPit(14 , 0));
expectedGame.setPits(kalahaPits);
Optional<KalahaGame> gameOptional= Optional.of(expectedGame);
Mockito.when(kalahaGameRepository.findById("5d414dcd24e4990006e7c9d3")).thenReturn(gameOptional);
KalahaGame gameInstance = kalahaGameService.loadGame("5d414dcd24e4990006e7c9d3");
BDDAssertions.then(gameInstance.getId()).isEqualTo("5d414dcd24e4990006e7c9d3");
BDDAssertions.then(gameInstance.getPlayerTurn()).isNull();
BDDAssertions.then (gameInstance.getPits()).size().isEqualTo(14);
BDDAssertions.then (gameInstance.getPit(1).getStones()).isEqualTo(6);
BDDAssertions.then (gameInstance.getPit(2).getStones()).isEqualTo(6);
BDDAssertions.then (gameInstance.getPit(3).getStones()).isEqualTo(6);
BDDAssertions.then (gameInstance.getPit(4).getStones()).isEqualTo(6);
BDDAssertions.then (gameInstance.getPit(5).getStones()).isEqualTo(6);
BDDAssertions.then (gameInstance.getPit(6).getStones()).isEqualTo(6);
BDDAssertions.then (gameInstance.getPit(7).getStones()).isEqualTo(0);
BDDAssertions.then (gameInstance.getPit(8).getStones()).isEqualTo(6);
BDDAssertions.then (gameInstance.getPit(9).getStones()).isEqualTo(6);
BDDAssertions.then (gameInstance.getPit(10).getStones()).isEqualTo(6);
BDDAssertions.then (gameInstance.getPit(11).getStones()).isEqualTo(6);
BDDAssertions.then (gameInstance.getPit(12).getStones()).isEqualTo(6);
BDDAssertions.then (gameInstance.getPit(13).getStones()).isEqualTo(6);
BDDAssertions.then (gameInstance.getPit(14).getStones()).isEqualTo(0);
}
@Test
public void testUpdatingGameInstanceIntoRepository () throws Exception {
KalahaGame expectedGame = new KalahaGame("5d414dcd24e4990006e7c9d3" , 6);
List<KalahaPit> kalahaPits = Arrays.asList(
new KalahaPit(1 , 6),
new KalahaPit(2 , 6),
new KalahaPit(3 , 6),
new KalahaPit(4 , 6),
new KalahaPit(5 , 6),
new KalahaPit(6 , 6),
new KalahaPit(7 , 0),
new KalahaPit(8 , 6),
new KalahaPit(9 , 6),
new KalahaPit(10 , 6),
new KalahaPit(11 , 6),
new KalahaPit(12 , 6),
new KalahaPit(13 , 6),
new KalahaPit(14 , 0));
expectedGame.setPits(kalahaPits);
Mockito.when(kalahaGameRepository.save(expectedGame)).thenReturn(expectedGame);
KalahaGame gameInstance = kalahaGameService.updateGame(expectedGame);
BDDAssertions.then(gameInstance.getId()).isEqualTo("5d414dcd24e4990006e7c9d3");
BDDAssertions.then(gameInstance.getPlayerTurn()).isNull();
BDDAssertions.then (gameInstance.getPits()).size().isEqualTo(14);
BDDAssertions.then (gameInstance.getPit(1).getStones()).isEqualTo(6);
BDDAssertions.then (gameInstance.getPit(2).getStones()).isEqualTo(6);
BDDAssertions.then (gameInstance.getPit(3).getStones()).isEqualTo(6);
BDDAssertions.then (gameInstance.getPit(4).getStones()).isEqualTo(6);
BDDAssertions.then (gameInstance.getPit(5).getStones()).isEqualTo(6);
BDDAssertions.then (gameInstance.getPit(6).getStones()).isEqualTo(6);
BDDAssertions.then (gameInstance.getPit(7).getStones()).isEqualTo(0);
BDDAssertions.then (gameInstance.getPit(8).getStones()).isEqualTo(6);
BDDAssertions.then (gameInstance.getPit(9).getStones()).isEqualTo(6);
BDDAssertions.then (gameInstance.getPit(10).getStones()).isEqualTo(6);
BDDAssertions.then (gameInstance.getPit(11).getStones()).isEqualTo(6);
BDDAssertions.then (gameInstance.getPit(12).getStones()).isEqualTo(6);
BDDAssertions.then (gameInstance.getPit(13).getStones()).isEqualTo(6);
BDDAssertions.then (gameInstance.getPit(14).getStones()).isEqualTo(0);
}
}
MancalaController tests
Spring Boot provides an easy and straightforward way to test your controller classes. Within the spring boot project, you can use this feature by adding @AutoConfigureMockMvc annotation to your Test class and then use @Autowired annotation for wiring the MockMvc object into your Test class as below and start writing tests for your controller operations.
As you can see in the below code, I have used @MockBean annotation to mock the MancalaGameService class which is used internally in our controller class to provides access to our Data storage mechanism for KalahaGame instances. Again, below is just a few examples of tests which can be written for testing the MancalaController class and you can definitely add many more tests to this class to make sure all different scenarios have been covered carefully:
@AutoConfigureMockMvc
@SpringBootTest (webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, properties = "server.port=0")
@RunWith(SpringRunner.class)
@DirtiesContext
public class KalahaGameControllerTests {
@MockBean
KalahaGameService mancalaGameService;
private final Resource jsonOfJustCreatedKalahaGame = new ClassPathResource("mancala-creation.json");
private final Resource jsonOfKalahaGameSowPit2JustAfterCreation = new ClassPathResource("mancala-sow-2.json");
@Autowired
private MockMvc mockMvc;
@SneakyThrows
private String asJson(Resource resource) {
return StreamUtils.copyToString(resource.getInputStream(), Charset.defaultCharset());
}
@Test
public void testGameCreation() throws Exception {
KalahaGame expectedGame = new KalahaGame("5d414dcd24e4990006e7c900", 6);
Mockito.when(this.mancalaGameService.createGame(6))
.thenReturn(expectedGame);
this.mockMvc.perform(post("/games"))
.andExpect(status().is2xxSuccessful())
.andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE))
.andExpect(content().json(asJson(jsonOfJustCreatedKalahaGame), false))
.andReturn();
}
@Test
public void testSowPitIndex2() throws Exception {
KalahaGame expectedGame = new KalahaGame("5d414dcd24e4990006e7c900", 6);
Mockito.when(mancalaGameService.loadGame("5d414dcd24e4990006e7c900"))
.thenReturn(expectedGame);
this.mockMvc.perform(put("/games/5d414dcd24e4990006e7c900/pits/2"))
.andExpect(status().is2xxSuccessful())
.andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE))
.andExpect(content().json(asJson(jsonOfKalahaGameSowPit2JustAfterCreation)))
.andReturn();
}
@Test
public void testSowingTheGameOfInvalidId() throws Exception {
Mockito.when(mancalaGameService.loadGame("5d414dcd24e4990006e7c902"))
.thenThrow(new ResourceNotFoundException("Game id 5d414dcd24e4990006e7c902 not found!"));
this.mockMvc.perform(put("/games/5d414dcd24e4990006e7c902/pits/2"))
.andExpect(status().is4xxClientError())
.andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE))
.andExpect(jsonPath("@.message").value("Game id 5d414dcd24e4990006e7c902 not found!"))
.andReturn();
}
@Test
public void testSowingTheGameAtInvalidPitIndex() throws Exception {
this.mockMvc.perform(put("/games/5d414dcd24e4990006e7c900/pits/7"))
.andExpect(status().is5xxServerError())
.andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE))
.andExpect(jsonPath("@.message").value("Invalid pit Index!. It should be between 1..6 or 8..13"))
.andReturn();
}
}
Now, we will need to create some integration tests for our Mancala game implementation projects to make sure the two microservices are communicating properly through APIs designed in Mancala-api microservice.
Integration Tests
This class performs various integration tests between 'mancala-web' and 'mancala-api' microservices using Wiremock. "WireMock is a simulator for HTTP-based APIs. Some might consider it a service virtualization tool or a mock server. It enables you to stay productive when an API you depend on doesn't exist or isn't complete."
Spring provides an easy way of integrating Wiremock to our project by below dependency to pom.cml file for 'mancala-web' microservice project:
<!-- WireMock tests -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-contract-wiremock</artifactId>
<scope>test</scope>
</dependency>
and then adding @AutoConfigureWireMock annotation to our test class.
@SpringBootTest (classes = MancalaWebApplication.class)
@RunWith(SpringRunner.class)
@AutoConfigureWireMock (port = 8080)
@DirtiesContext
public class MancalaIntegrationTests {
As you can see in the above snippet code, we have configured the Wiremock to run on port 8080 meaning that we are simulating our 'mancala-api' service on a simulated server running on port 8080. Now let's discuss how to write the integration test using Wiremock:
Within 'mancala-web' microservice we have used Ribbon client load balancing to communicate with 'mancala-api' microservice and therefore, we have created MancalaClientConfig class as below to provide the dynamic URL for 'mancala-api' REST service based on the availability of instances within Consul service registry:
package com.dzone.mancala.web.client;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.stereotype.Component;
@Component
public class MancalaClientConfig {
private LoadBalancerClient loadBalancer;
@Value("${mancala.api.service.id}")
private final String apiServiceId = "mancala-api";
@Autowired
public void setLoadBalancer(LoadBalancerClient loadBalancer) {
this.loadBalancer = loadBalancer;
}
public String getNewMancalaGameUrl(){
ServiceInstance instance = this.loadBalancer.choose(apiServiceId);
String url = String.format("http://%s:%s/games", instance.getHost(), instance.getPort());
return url;
}
public String getSowMancalaGameUrl(String gameId, Integer pitIndex){
ServiceInstance instance = this.loadBalancer.choose(apiServiceId);
String url = String.format("http://%s:%s/games/%s/pits/%s", instance.getHost(), instance.getPort(), gameId, pitIndex);
return url;
}
}
and then, we have created a MancalaClient class to use the above class to retrieve the service URL and invoke the 'mancala-api' service using Spring boot RestTemplate class.
package com.dzone.mancala.web.client;
import com.dzone.mancala.web.model.KalahaGame;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;
/*
This class performs the api invocation for the web client
*/
@Component
@Slf4j
public class MancalaClient {
private RestTemplate restTemplate;
@Autowired
private MancalaClientConfig mancalaClientConfig;
public MancalaClient(@Autowired RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
public KalahaGame startNewMancalaGame() throws Exception {
String url = mancalaClientConfig.getNewMancalaGameUrl();
log.info("calling:" + url);
ResponseEntity<KalahaGame> gameResponse = this.restTemplate.postForEntity(url, null, KalahaGame.class);
log.info("response: " + new ObjectMapper().writerWithDefaultPrettyPrinter().
writeValueAsString(gameResponse.getBody()));
return gameResponse.getBody();
}
public KalahaGame sowMancalaGame(String gameId, Integer pitIndex) throws Exception {
String url = mancalaClientConfig.getSowMancalaGameUrl(gameId, pitIndex);
log.info("calling: " + url);
ResponseEntity<KalahaGame> response = restTemplate.exchange(url, HttpMethod.PUT, null, KalahaGame.class);
log.info("response: " + new ObjectMapper().writerWithDefaultPrettyPrinter().
writeValueAsString(response.getBody()));
return response.getBody();
}
}
Therefore in order to create integration tests, we will need to mock the MancalaClientConfig class using @MockBean annotation:
@MockBeanMancalaClientConfig mancalaClientConfig;
We have also added two sample JSON files just for the purpose of writing below tests in the resources folder under the 'test' package and use below snippet code to access them:
private final Resource mancalaCreate = new ClassPathResource("mancala-creation.json");
private final Resource mancalaSow2 = new ClassPathResource("mancala-sow-2.json");
@SneakyThrows
private String asJson(Resource resource) {
return StreamUtils.copyToString(resource.getInputStream(), Charset.defaultCharset());
}
and here is the final MancalaIntegrationTests class. Notice to below snippet code:
Mockito.when(mancalaClientConfig.getNewMancalaGameUrl()).
thenReturn("http://localhost:8080/games");
which returns the API URL based on Wiremock configuration we did on port 8080. Below snippet code trains the Wiremock service simulator to return our expected result upon invocation of specific URL with given parameters:
WireMock.stubFor(WireMock.post("/games")
.willReturn(WireMock.aResponse()
.withHeader(HttpHeaders.CONTENT_TYPE,
MediaType.APPLICATION_JSON_UTF8_VALUE)
.withStatus(HttpStatus.OK.value())
.withBody(asJson(mancalaCreate))
));
Now, when we call the service API through MancalaClient, we will expect to receive a similar response and therefore we can now place necessary assertation using BDDAssertions class:
/*
Using Spring Cloud Contract Wiremock for inter-service communication testing between Mancala microservices
*/
@SpringBootTest (classes = MancalaWebApplication.class)
@RunWith(SpringRunner.class)
@AutoConfigureWireMock (port = 8080)
@DirtiesContext
public class MancalaIntegrationTests {
private final Resource mancalaCreate = new ClassPathResource("mancala-creation.json");
private final Resource mancalaSow2 = new ClassPathResource("mancala-sow-2.json");
@MockBean
MancalaClientConfig mancalaClientConfig;
@Autowired
private MancalaClient mancalaClient;
@SneakyThrows
private String asJson(Resource resource) {
return StreamUtils.copyToString(resource.getInputStream(), Charset.defaultCharset());
}
@Test
public void testManacalaCreation () throws Exception{
Mockito.when(mancalaClientConfig.getNewMancalaGameUrl()).thenReturn("http://localhost:8080/games");
WireMock.stubFor(WireMock.post("/games")
.willReturn(WireMock.aResponse()
.withHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_UTF8_VALUE)
.withStatus(HttpStatus.OK.value())
.withBody(asJson(mancalaCreate))
));
KalahaGame kalahaGame = mancalaClient.startNewMancalaGame();
List<KalahaPit> kalahaPits = Arrays.asList(
new KalahaPit(1 , 6),
new KalahaPit(2 , 6),
new KalahaPit(3 , 6),
new KalahaPit(4 , 6),
new KalahaPit(5 , 6),
new KalahaPit(6 , 6),
new KalahaPit(7 , 0),
new KalahaPit(8 , 6),
new KalahaPit(9 , 6),
new KalahaPit(10 , 6),
new KalahaPit(11 , 6),
new KalahaPit(12 , 6),
new KalahaPit(13 , 6),
new KalahaPit(14 , 0));
BDDAssertions.then(kalahaGame.getPlayerTurn()).isNull();
BDDAssertions.then(kalahaGame.getPits()).isEqualTo(kalahaPits);
BDDAssertions.then(kalahaGame.getLeftHouseStones()).isEqualTo(0);
BDDAssertions.then(kalahaGame.getRightHouseStones()).isEqualTo(0);
}
}
We can do similarly for testing the sowing operation of our 'mancala-api' service. As you can see in below code, to test the sowing operation we will need to repeat the Mancala game creation test first and the perform sowing on that game instance afterward:
/*
We first need to run the Mancala Game creation test and use the game id generated to sow the game
*/
@Test
public void testManacalaSowPitIndex2 () throws Exception {
// 1. Run the Mancala Creation Test
Mockito.when(mancalaClientConfig.getNewMancalaGameUrl()).thenReturn("http://localhost:8080/games");
WireMock.stubFor(WireMock.post("/games")
.willReturn(WireMock.aResponse()
.withHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_UTF8_VALUE)
.withStatus(HttpStatus.OK.value())
.withBody(asJson(mancalaCreate))
));
KalahaGame kalahaGame = mancalaClient.startNewMancalaGame();
List<KalahaPit> kalahaPits = Arrays.asList(
new KalahaPit(1 , 6),
new KalahaPit(2 , 6),
new KalahaPit(3 , 6),
new KalahaPit(4 , 6),
new KalahaPit(5 , 6),
new KalahaPit(6 , 6),
new KalahaPit(7 , 0),
new KalahaPit(8 , 6),
new KalahaPit(9 , 6),
new KalahaPit(10 , 6),
new KalahaPit(11 , 6),
new KalahaPit(12 , 6),
new KalahaPit(13 , 6),
new KalahaPit(14 , 0));
BDDAssertions.then(kalahaGame.getPlayerTurn()).isNull();
BDDAssertions.then(kalahaGame.getPits()).isEqualTo(kalahaPits);
BDDAssertions.then(kalahaGame.getLeftHouseStones()).isEqualTo(0);
BDDAssertions.then(kalahaGame.getRightHouseStones()).isEqualTo(0);
// 2. Run the Mancala Sow test for pit 2
Mockito.when(mancalaClientConfig.getSowMancalaGameUrl(kalahaGame.getId(), 2)).
thenReturn("http://localhost:8080/games/"+kalahaGame.getId()+"/pits/2");
WireMock.stubFor(WireMock.put("/games/"+kalahaGame.getId()+"/pits/2")
.willReturn(WireMock.aResponse()
.withHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_UTF8_VALUE)
.withStatus(HttpStatus.OK.value())
.withBody(asJson(mancalaSow2))
));
KalahaGame kalahaGameAfterSowingPit2 = mancalaClient.sowMancalaGame(kalahaGame.getId(), 2);
System.out.println(kalahaGameAfterSowingPit2);
List<KalahaPit> newKalahaPits = Arrays.asList(
new KalahaPit(1 , 6),
new KalahaPit(2 , 0),
new KalahaPit(3 , 7),
new KalahaPit(4 , 7),
new KalahaPit(5 , 7),
new KalahaPit(6 , 7),
new KalahaPit(7 , 1),
new KalahaPit(8 , 7),
new KalahaPit(9 , 6),
new KalahaPit(10 , 6),
new KalahaPit(11 , 6),
new KalahaPit(12 , 6),
new KalahaPit(13 , 6),
new KalahaPit(14 , 0));
BDDAssertions.then(kalahaGameAfterSowingPit2.getPlayerTurn()).isEqualTo("PlayerB");
BDDAssertions.then(kalahaGameAfterSowingPit2.getPits()).isEqualTo(newKalahaPits);
BDDAssertions.then(kalahaGameAfterSowingPit2.getLeftHouseStones()).isEqualTo(0);
BDDAssertions.then(kalahaGameAfterSowingPit2.getRightHouseStones()).isEqualTo(1);
}
The above two tests have been developed only for the purpose of this article but you can add several tests to make sure the client microservice will always receive a proper response from the server as long as the API implementation does not change over time. However, we all know that those changes to API implementation are inevitable after all and what happens is that we will receive unexpected behaviors from the clients in production time only which will ultimately bring customer unsatisfaction and bad user experience for our Game users. Here is the reason why this happens:
The 'mancala-api' microservice is developed and maintained by a separate team than 'mancala-web' microservice according to the Microservice philosophy therefore, it's quite obvious that the team responsible for 'mancala-api' microservice will perform many changes to the API implementation without notifying the order teams using this microservice and therefore, unless we do not publish the clients into production we will not notice those changes.
As a result, we need to make sure the API implementation and clients of that API remain always in complete sync for every update made by the API implementation team and that takes us to the next step which is Consumer Driver Contract Tests or CDC tests.
Consumer Driven Contract (CDC) Tests
When we are developing services based on microservices approach, we will need to make sure the client services (service invoker) are always in sync with the provider services. This happens through a contract made between the service provider and consumers of that service.
The contract which is usually developed by using groovy scripts defines what clients can expect upon invocation of particular API with given parameters and those responses are guaranteed by that service provider. Spring Cloud Contract provides APIs for developing CDC based tests for our microservices and it supports DSL for below languages:
- Groovy.
- YAML.
- Java.
- Kotlin.
To get familiar with details on how to write your DSL contracts using your favorite language, you may refer to the Spring boot documentation here. I have used groovy to create my contract DSLs.
Step One
First, we need to create our groovy scripts and place them under the resources/contracts folder of the 'mancala-api' project. Here are two examples of our 'mancala-api' implementation:
Example One
Groovy script for MancalaGame creation operation named shouldReturnNewlyCreatedMancalaGame.groovy
import org.springframework.cloud.contract.spec.Contract
import org.springframework.cloud.contract.spec.internal.HttpMethods
import org.springframework.http.MediaType
Contract.make {
description "should return a newly created Mancala Game "
request {
url("/games")
method(HttpMethods.HttpMethod.POST)
}
response {
status(200)
body(
["id" : "5d414dcd24e4990006e7c900",
"pits" : [
[
"id" : 1,
"stones": 6
],
[
"id" : 2,
"stones": 6
],
[
"id" : 3,
"stones": 6
],
[
"id" : 4,
"stones": 6
],
[
"id" : 5,
"stones": 6
],
[
"id" : 6,
"stones": 6
],
[
"id" : 7,
"stones": 0
],
[
"id" : 8,
"stones": 6
],
[
"id" : 9,
"stones": 6
],
[
"id" : 10,
"stones": 6
],
[
"id" : 11,
"stones": 6
],
[
"id" : 12,
"stones": 6
],
[
"id" : 13,
"stones": 6
],
[
"id" : 14,
"stones": 0
]
]
,
"playerTurn": null
]
)
headers {
contentType(MediaType.APPLICATION_JSON_VALUE)
}
}
}
Example Two
Groovy script for sowing the Mancala game for pit index 2 for instance, called shouldReturnMancalaGameSowingPit.groovy.
import org.springframework.cloud.contract.spec.Contract
import org.springframework.cloud.contract.spec.internal.HttpMethods
import org.springframework.http.MediaType
Contract.make {
description "should return a Mancala Game after Sowing of index 2"
request {
url("/games/5d414dcd24e4990006e7c900/pits/2")
method(HttpMethods.HttpMethod.PUT)
}
response {
status(200)
body(
"id": "5d414dcd24e4990006e7c900" ,
"pits": [
[
"id": 1,
"stones": 6
],
[
"id": 2,
"stones": 0
],
[
"id": 3,
"stones": 7
],
[
"id": 4,
"stones": 7
],
[
"id": 5,
"stones": 7
],
[
"id": 6,
"stones": 7
],
[
"id": 7,
"stones": 1
],
[
"id": 8,
"stones": 7
],
[
"id": 9,
"stones": 6
],
[
"id": 10,
"stones": 6
],
[
"id": 11,
"stones": 6
],
[
"id": 12,
"stones": 6
],
[
"id": 13,
"stones": 6
],
[
"id": 14,
"stones": 0
]
]
,
"playerTurn": "PlayerB"
)
headers {
contentType(MediaType.APPLICATION_JSON_VALUE)
}
}
}
Step Two
We then need to add below plugin provided by Spring boot into our API provider microservice, 'mancala-api':
<!-- Spring Cloud Contract Plugin-->
<plugin>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-contract-maven-plugin</artifactId>
<extensions>true</extensions>
<version>2.1.2.RELEASE</version>
<configuration>
<baseClassForTests>
com.dzone.mancala.game.cdc.BaseClass
</baseClassForTests>
<testMode>EXPLICIT</testMode>
</configuration>
</plugin>
The above plugin runs our DSL contracts placed in /resources/contracts folder and autogenerates the necessary integration test classes for them and will execute them against the actual implementation of those API each time the 'mancala-api' microservice is packaged and as a result, it generates stubs for those contracts to be used by clients of this microservice ('mancala-web') and then will package and place it in your local maven repository by default under the group id and artifact id as defined in pom.xml of 'mancala-api' project.
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.8.RELEASE</version>
<relativePath/>
</parent>
<groupId>com.dzone.mancalagame</groupId>
<artifactId>mancala-api</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>mancala-api</name>
<description>Spring project for building Mancala Game Api</description>
It performs this job by using the RestAssured library. So the next step is to setup RestAssured for our MancalaController class in given path defined in the plugin configuration: com.dzone.mancala.game.cdc.BaseClass
Step Three
In order to use Spring Cloud Contract library, we will need to add below dependency to our 'mancala-api' project pom.xml file:
<!-- Spring Cloud Contract-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-contract-verifier</artifactId>
<scope>test</scope>
</dependency>
To setup RestAssured using a dynamic port, we need to use @LocalServerPort annotation to get the running port and assign it to RestAssure library by providing the full URL or assign the given port directly in our setup method annotated with @Before:
package com.dzone.mancala.game.cdc;
import com.dzone.mancala.game.controller.MancalaController;
import com.dzone.mancala.game.model.KalahaGame;
import com.dzone.mancala.game.model.KalahaPit;
import com.dzone.mancala.game.model.PlayerTurns;
import com.dzone.mancala.game.service.KalahaGameService;
import com.dzone.mancala.game.service.MancalaSowingService;
import io.restassured.RestAssured;
import io.restassured.module.mockmvc.RestAssuredMockMvc;
import org.junit.Before;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.boot.web.server.LocalServerPort;
import org.springframework.context.annotation.Import;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit4.SpringRunner;
import java.util.Arrays;
import java.util.List;
/*
This is used for Spring Contact Testing. It prepares the Stub dependencies
*/
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, properties = "server.port=0")
@RunWith(SpringRunner.class)
@Import({MancalaController.class, KalahaGameService.class})
@DirtiesContext
public class BaseClass {
@LocalServerPort
private String port;
@MockBean
private KalahaGameService mancalaGameService;
@MockBean
private MancalaSowingService mancalaSowingService;
@Autowired
private MancalaController mancalaController;
@Before
public void setupGame() throws Exception {
RestAssured.baseURI = "http://localhost:" + this.port;
KalahaGame expectedGame = new KalahaGame("5d414dcd24e4990006e7c900", 6);
Mockito.when(this.mancalaGameService.createGame(6))
.thenReturn(expectedGame);
Mockito.when(mancalaGameService.loadGame("5d414dcd24e4990006e7c900"))
.thenReturn(expectedGame);
List<KalahaPit> pitsAfterSowingIndex2 = Arrays.asList(
new KalahaPit(1, 6),
new KalahaPit(2, 0),
new KalahaPit(3, 7),
new KalahaPit(4, 7),
new KalahaPit(5, 7),
new KalahaPit(6, 7),
new KalahaPit(7, 1),
new KalahaPit(8, 7),
new KalahaPit(9, 6),
new KalahaPit(10, 6),
new KalahaPit(11, 6),
new KalahaPit(12, 6),
new KalahaPit(13, 6),
new KalahaPit(14, 0));
KalahaGame gameAfterSowingIndex2 = new KalahaGame("5d414dcd24e4990006e7c900", 6);
gameAfterSowingIndex2.setPits(pitsAfterSowingIndex2);
gameAfterSowingIndex2.setPlayerTurn(PlayerTurns.PlayerB);
Mockito.when(this.mancalaSowingService.sow(expectedGame, 2))
.thenReturn(gameAfterSowingIndex2);
RestAssuredMockMvc.standaloneSetup(mancalaController);
}
}
As you can see in the above code, in order to Mock the whole MancalaController class using RestAssuredMockMvc.standaloneSetup() method, we need to use @MockBean for MancalaGameService as well as MancalaSowingService classes used with MancalaController and use Mockito.when() method to provide the expected response while invoking the corresponding methods in those classes.
Step Four
Now, we run 'mvc clean install' command to install the plugin and run the groovy scripts:
[.\mancala-game\mancala-microservice] ./mvnw clean install
This will generate the stub classes and places it into your local maven repository:
[INFO] --- maven-install-plugin:2.5.2:install (default-install) @ mancala-microservice ---
[INFO] Installing D:\mancala-game\mancala-microservice\pom.xml to C:\Users\[Your-User]\.m2\repository\com\dzone\mancalagame\mancala-microservice\0.0.1-SNAPSHOT\mancala-microservice-0.0.1-SNAPSHOT.pom
Next, we need to use the generated stub in our 'mancala-web' client microservice and validate the REST call communications between these two microservices with the help of the Spring cloud contract stub runner library.
Step Five
The final step for our Consumer Driver Contract Test (CDC) is to create the CDCApplicationTest class within our 'mancala-web' client microservice and configure the Spring Cloud Contract Stub Runner library with the help of @AutoConfigureStubRunner annotation and use the previously generated stub for the 'mancala-api' service (which by default is stored in your local maven repository) to validate it. To use this library, we will need to add below dependency to our pom.xml file:
<!-- Spring Stub Runner for CDC tests -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-contract-stub-runner</artifactId>
<scope>test</scope>
</dependency>
To locate the stub package, we need to specify the group id and artifact id previously used to generate it which in our project will be as below:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.dzone.mancalagame</groupId>
<artifactId>mancala-api</artifactId>
Therefore, the final configuration for the@AutoConfigureStubRunner annotation will be as below:
@AutoConfigureStubRunner (
ids = "com.dzone.mancalagame:mancala-api:+:8080",
stubsMode = StubRunnerProperties.StubsMode.LOCAL)
It's very important to specify the correct group id and artifact id otherwise the Stub Runner library will not found the stub and you will get an error at the time of executing the CDCTest.
Here is the final implementation of CDCApplicationTests class for our Mancala-api service:
@AutoConfigureStubRunner (
ids = "com.dzone.mancalagame:mancala-api:+:8080",
stubsMode = StubRunnerProperties.StubsMode.LOCAL)
@AutoConfigureJson
@SpringBootTest (classes = MancalaWebApplication.class)
@RunWith(SpringRunner.class)
@DirtiesContext
public class CDCApplicationTests {
@Autowired
private MancalaClient mancalaClient;
@Test
public void testManacalaCreation () throws Exception {
KalahaGame kalahaGame = mancalaClient.startNewMancalaGame();
List<KalahaPit> kalahaPits = Arrays.asList(
new KalahaPit(1 , 6),
new KalahaPit(2 , 6),
new KalahaPit(3 , 6),
new KalahaPit(4 , 6),
new KalahaPit(5 , 6),
new KalahaPit(6 , 6),
new KalahaPit(7 , 0),
new KalahaPit(8 , 6),
new KalahaPit(9 , 6),
new KalahaPit(10 , 6),
new KalahaPit(11 , 6),
new KalahaPit(12 , 6),
new KalahaPit(13 , 6),
new KalahaPit(14 , 0));
BDDAssertions.then(kalahaGame.getPlayerTurn()).isNull();
BDDAssertions.then(kalahaGame.getPits()).isEqualTo(kalahaPits);
BDDAssertions.then(kalahaGame.getLeftHouseStones()).isEqualTo(0);
BDDAssertions.then(kalahaGame.getRightHouseStones()).isEqualTo(0);
}
/*
We first need to run the Mancala Game creation test and use the game id generated to sow the game
*/
@Test
public void testManacalaSowingPitIndex2 () throws Exception {
KalahaGame kalahaGame = mancalaClient.startNewMancalaGame();
List<KalahaPit> kalahaPits = Arrays.asList(
new KalahaPit(1 , 6),
new KalahaPit(2 , 6),
new KalahaPit(3 , 6),
new KalahaPit(4 , 6),
new KalahaPit(5 , 6),
new KalahaPit(6 , 6),
new KalahaPit(7 , 0),
new KalahaPit(8 , 6),
new KalahaPit(9 , 6),
new KalahaPit(10 , 6),
new KalahaPit(11 , 6),
new KalahaPit(12 , 6),
new KalahaPit(13 , 6),
new KalahaPit(14 , 0));
BDDAssertions.then(kalahaGame.getPlayerTurn()).isNull();
BDDAssertions.then(kalahaGame.getPits()).isEqualTo(kalahaPits);
BDDAssertions.then(kalahaGame.getLeftHouseStones()).isEqualTo(0);
BDDAssertions.then(kalahaGame.getRightHouseStones()).isEqualTo(0);
// 2. Run the Mancala Sow test for pit 2
KalahaGame kalahaGameAfterSowingPit2 = mancalaClient.sowMancalaGame(kalahaGame.getId(), 2);
System.out.println(kalahaGameAfterSowingPit2);
List<KalahaPit> newKalahaPits = Arrays.asList(
new KalahaPit(1 , 6),
new KalahaPit(2 , 0),
new KalahaPit(3 , 7),
new KalahaPit(4 , 7),
new KalahaPit(5 , 7),
new KalahaPit(6 , 7),
new KalahaPit(7 , 1),
new KalahaPit(8 , 7),
new KalahaPit(9 , 6),
new KalahaPit(10 , 6),
new KalahaPit(11 , 6),
new KalahaPit(12 , 6),
new KalahaPit(13 , 6),
new KalahaPit(14 , 0));
BDDAssertions.then(kalahaGameAfterSowingPit2.getPlayerTurn()).isEqualTo("PlayerB");
BDDAssertions.then(kalahaGameAfterSowingPit2.getPits()).isEqualTo(newKalahaPits);
BDDAssertions.then(kalahaGameAfterSowingPit2.getLeftHouseStones()).isEqualTo(0);
BDDAssertions.then(kalahaGameAfterSowingPit2.getRightHouseStones()).isEqualTo(1);
}
}
The above sample tests are given for the purpose of this article but you can develop many more tests to cover all variations of the API calls for this project.
Now, we can run 'mvn clean install' command to validate our CDC tests:
D:\mancala-game\mancala-microservice>mvn clean install
[INFO] Scanning for projects...
[INFO] ------------------------------------------------------------------------
[INFO] Reactor Build Order:
[INFO]
[INFO] mancala-api [jar]
[INFO] mancala-web [jar]
[INFO] mancala-microservice [pom]
[INFO]
[INFO] -----------------< com.dzone.mancalagame:mancala-api >------------------
[INFO] Building mancala-api 0.0.1-SNAPSHOT [1/3]
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- maven-clean-plugin:3.1.0:clean (default-clean) @ mancala-api ---
[INFO] Deleting D:\mancala-game\mancala-microservice\mancala-api\target
[INFO]
[INFO] --- maven-resources-plugin:3.1.0:resources (default-resources) @ mancala-api ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Copying 1 resource
[INFO] Copying 2 resources
[INFO]
[INFO] --- maven-compiler-plugin:3.8.1:compile (default-compile) @ mancala-api ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 17 source files to D:\mancala-game\mancala-microservice\mancala-api\target\classes
[INFO]
[INFO] --- spring-cloud-contract-maven-plugin:2.1.2.RELEASE:generateTests (default-generateTests) @ mancala-api ---
[INFO] Generating server tests source code for Spring Cloud Contract Verifier contract verification
[INFO] Will use contracts provided in the folder [D:\mancala-game\mancala-microservice\mancala-api\src\test\resources\contracts]
[INFO] Directory with contract is present at [D:\mancala-game\mancala-microservice\mancala-api\src\test\resources\contracts]
[INFO] Test Source directory: D:\mancala-game\mancala-microservice\mancala-api\target\generated-test-sources\contracts added.
[INFO] Using [com.dzone.mancala.game.cdc.BaseClass] as base class for test classes, [null] as base package for tests, [null] as package with base classes, base class mappings []
[INFO] Creating new class file [D:\mancala-game\mancala-microservice\mancala-api\target\generated-test-sources\contracts\com\dzone\mancala\game\cdc\ContractVerifierTest.java]
[INFO] Generated 1 test classes.
[INFO]
[INFO] --- spring-cloud-contract-maven-plugin:2.1.2.RELEASE:convert (default-convert) @ mancala-api ---
[INFO] Will use contracts provided in the folder [D:\mancala-game\mancala-microservice\mancala-api\src\test\resources\contracts]
[INFO] Copying Spring Cloud Contract Verifier contracts to [D:\mancala-game\mancala-microservice\mancala-api\target\stubs\META-INF\com.dzone.mancalagame\mancala-api\0.0.1-SNAPSHOT\contracts]. Only files matching [.*] pattern will end up in the final JAR with stubs.
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Copying 2 resources
[INFO] Converting from Spring Cloud Contract Verifier contracts to WireMock stubs mappings
[INFO] Spring Cloud Contract Verifier contracts directory: D:\mancala-game\mancala-microservice\mancala-api\src\test\resources\contracts
[INFO] Stub Server stubs mappings directory: D:\mancala-game\mancala-microservice\mancala-api\target\stubs\META-INF\com.dzone.mancalagame\mancala-api\0.0.1-SNAPSHOT\mappings
[INFO] Creating new stub [D:\mancala-game\mancala-microservice\mancala-api\target\stubs\META-INF\com.dzone.mancalagame\mancala-api\0.0.1-SNAPSHOT\mappings\shouldReturnMancalaGameSowingPit.json]
[INFO] Creating new stub [D:\mancala-game\mancala-microservice\mancala-api\target\stubs\META-INF\com.dzone.mancalagame\mancala-api\0.0.1-SNAPSHOT\mappings\shouldReturnNewlyCreatedMancalaGame.json]
[INFO]
[INFO] --- maven-resources-plugin:3.1.0:testResources (default-testResources) @ mancala-api ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Copying 6 resources
[INFO] skip non existing resourceDirectory D:\mancala-game\mancala-microservice\mancala-api\target\generated-test-resources\contracts
[INFO]
[INFO] --- maven-compiler-plugin:3.8.1:testCompile (default-testCompile) @ mancala-api ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 7 source files to D:\mancala-game\mancala-microservice\mancala-api\target\test-classes
[INFO]
[INFO] --- maven-surefire-plugin:2.22.2:test (default-test) @ mancala-api ---
[INFO]
[INFO] -------------------------------------------------------
[INFO] T E S T S
[INFO] -------------------------------------------------------
[INFO] Running com.dzone.mancala.game.cdc.ContractVerifierTest
...
...
[INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 30.001 s - in com.dzone.mancala.game.cdc.ContractVerifierTest
[INFO] Running com.dzone.mancala.game.controller.KalahaGameControllerTests
...
...
[INFO] Tests run: 4, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 19.346 s - in com.dzone.mancala.game.controller.KalahaGameControllerTests
[INFO] Running com.dzone.mancala.game.repository.KalahaGameRepositoryTests
...
...
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 6.849 s - in com.dzone.mancala.game.repository.KalahaGameRepositoryTests
[INFO] Running com.dzone.mancala.game.service.KalahaGameServiceTests
....
....
[INFO] Tests run: 3, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 16.977 s - in com.dzone.mancala.game.service.KalahaGameServiceTests
[INFO] Running com.dzone.mancala.game.service.MancalaSowingServiceTests
[INFO] Tests run: 9, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.024 s - in com.dzone.mancala.game.service.MancalaSowingServiceTests
2019-10-12 13:19:12.378 INFO [mancala-api,,,] 12776 --- [ Thread-21] c.n.l.PollingServerListUpdater : Shutting down the Executor Pool for PollingServerListUpdater
[INFO]
[INFO] Results:
[INFO]
[INFO] Tests run: 19, Failures: 0, Errors: 0, Skipped: 0
[INFO]
[INFO]
[INFO] --- spring-cloud-contract-maven-plugin:2.1.2.RELEASE:generateStubs (default-generateStubs) @ mancala-api ---
[INFO] Files matching this pattern will be excluded from stubs generation []
[INFO] Building jar: D:\mancala-game\mancala-microservice\mancala-api\target\mancala-api-0.0.1-SNAPSHOT-stubs.jar
[INFO]
[INFO] --- maven-jar-plugin:3.1.2:jar (default-jar) @ mancala-api ---
[INFO] Building jar: D:\mancala-game\mancala-microservice\mancala-api\target\mancala-api-0.0.1-SNAPSHOT.jar
[INFO]
[INFO] --- spring-boot-maven-plugin:2.1.8.RELEASE:repackage (repackage) @ mancala-api ---
[INFO] Replacing main artifact with repackaged archive
[INFO]
[INFO] --- maven-install-plugin:2.5.2:install (default-install) @ mancala-api ---
[INFO] Installing D:\mancala-game\mancala-microservice\mancala-api\target\mancala-api-0.0.1-SNAPSHOT.jar to C:\Users\Talebi\.m2\repository\com\dzone\mancalagame\mancala-api\0.0.1-SNAPSHOT\mancala-api-0.0.1-SNAPSHOT.jar
[INFO] Installing D:\mancala-game\mancala-microservice\mancala-api\pom.xml to C:\Users\Talebi\.m2\repository\com\dzone\mancalagame\mancala-api\0.0.1-SNAPSHOT\mancala-api-0.0.1-SNAPSHOT.pom
[INFO] Installing D:\mancala-game\mancala-microservice\mancala-api\target\mancala-api-0.0.1-SNAPSHOT-stubs.jar to C:\Users\Talebi\.m2\repository\com\dzone\mancalagame\mancala-api\0.0.1-SNAPSHOT\mancala-api-0.0.1-SNAPSHOT-stubs.jar
[INFO]
[INFO] -----------------< com.dzone.mancalagame:mancala-web >------------------
[INFO] Building mancala-web 0.1.0 [2/3]
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- maven-clean-plugin:3.1.0:clean (default-clean) @ mancala-web ---
[INFO] Deleting D:\mancala-game\mancala-microservice\mancala-web\target
[INFO]
[INFO] --- maven-resources-plugin:3.1.0:resources (default-resources) @ mancala-web ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Copying 1 resource
[INFO] Copying 2 resources
[INFO]
[INFO] --- maven-compiler-plugin:3.8.1:compile (default-compile) @ mancala-web ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 14 source files to D:\mancala-game\mancala-microservice\mancala-web\target\classes
[INFO]
[INFO] --- maven-resources-plugin:3.1.0:testResources (default-testResources) @ mancala-web ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Copying 3 resources
[INFO]
[INFO] --- maven-compiler-plugin:3.8.1:testCompile (default-testCompile) @ mancala-web ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 2 source files to D:\mancala-game\mancala-microservice\mancala-web\target\test-classes
[INFO]
[INFO] --- maven-surefire-plugin:2.22.2:test (default-test) @ mancala-web ---
[INFO]
[INFO] -------------------------------------------------------
[INFO] T E S T S
[INFO] -------------------------------------------------------
[INFO] Running com.dzone.mancala.web.client.CDCApplicationTests
13:19:36.022 [main] DEBUG org.springframework.test.context.junit4.SpringJUnit4ClassRunner - SpringJUnit4ClassRunner constructor called with [class com.dzone.mancala.web.client.CDCApplicationTests]
...
...
[INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 38.665 s - in com.dzone.mancala.web.client.CDCApplicationTests
[INFO] Running com.dzone.mancala.web.client.MancalaIntegrationTests
...
...
[INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 9.815 s - in com.dzone.mancala.web.client.MancalaIntegrationTests
2019-10-12 13:20:24.280 INFO [mancala-web,,,] 4484 --- [ Thread-21] c.n.l.PollingServerListUpdater : Shutting down the Executor Pool for PollingServerListUpdater
[INFO]
[INFO] Results:
[INFO]
[INFO] Tests run: 4, Failures: 0, Errors: 0, Skipped: 0
[INFO]
[INFO]
[INFO] --- maven-jar-plugin:3.1.2:jar (default-jar) @ mancala-web ---
[INFO] Building jar: D:\mancala-game\mancala-microservice\mancala-web\target\mancala-web-0.1.0.jar
[INFO]
[INFO] --- spring-boot-maven-plugin:2.1.8.RELEASE:repackage (repackage) @ mancala-web ---
[INFO] Replacing main artifact with repackaged archive
[INFO]
[INFO] --- maven-install-plugin:2.5.2:install (default-install) @ mancala-web ---
[INFO] Installing D:\mancala-game\mancala-microservice\mancala-web\target\mancala-web-0.1.0.jar to C:\Users\Talebi\.m2\repository\com\dzone\mancalagame\mancala-web\0.1.0\mancala-web-0.1.0.jar
[INFO] Installing D:\mancala-game\mancala-microservice\mancala-web\pom.xml to C:\Users\Your-OS-User\.m2\repository\com\dzone\mancalagame\mancala-web\0.1.0\mancala-web-0.1.0.pom
[INFO]
[INFO] -------------< com.dzone.mancalagame:mancala-microservice >-------------
[INFO] Building mancala-microservice 0.0.1-SNAPSHOT [3/3]
[INFO] --------------------------------[ pom ]---------------------------------
[INFO]
[INFO] --- maven-clean-plugin:3.0.0:clean (default-clean) @ mancala-microservice ---
[INFO]
[INFO] --- spring-boot-maven-plugin:2.0.6.RELEASE:repackage (default) @ mancala-microservice ---
[INFO]
[INFO] --- maven-install-plugin:2.5.2:install (default-install) @ mancala-microservice ---
[INFO] Installing D:\mancala-game\mancala-microservice\pom.xml to C:\Users\[Your-OS-User]\.m2\repository\com\dzone\mancalagame\mancala-microservice\0.0.1-SNAPSHOT\mancala-microservice-0.0.1-SNAPSHOT.pom
[INFO] ------------------------------------------------------------------------
[INFO] Reactor Summary:
[INFO]
[INFO] mancala-api 0.0.1-SNAPSHOT ......................... SUCCESS [01:52 min]
[INFO] mancala-web 0.1.0 .................................. SUCCESS [01:05 min]
[INFO] mancala-microservice 0.0.1-SNAPSHOT ................ SUCCESS [ 6.679 s]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 03:16 min
[INFO] Finished at: 2019-10-12T13:20:33+03:30
[INFO] ------------------------------------------------------------------------
Please note that we have run this command in the parent project called mancala-microservice. Any time we try to package the mancala-microservice project (the root project of the two microservices) and use the artifacts to be placed into production, a fresh copy of stub will be generated by 'mancala-api' project and will be placed in your local maven repository which will is used again by 'mancala-web' project to test client implementation against the API provider.
Therefore, if there would be any changes within the API signature, it will be caught just here. This ensures that both the producer and consumers of the API remain in sync all the time and there would not be any surprise exceptions in your production environment.
The complete source code for this article is available in my GitHub.
To build and run this application, please follow the instructions I have provided here.
Your valuable feedback and comments are highly appreciated!
Further Reading
Opinions expressed by DZone contributors are their own.
Trending
-
Harnessing the Power of Integration Testing
-
Guide To Selecting the Right GitOps Tool - Argo CD or Flux CD
-
Batch Request Processing With API Gateway
-
Security Challenges for Microservice Applications in Multi-Cloud Environments
Comments