Microservices Patterns
Testing Microservices IV
Writing Unit Tests For a Service
● Imagine that you want to write a test that verifies Order Service correctly calculates
the subtotal of an Order.
● You could write tests that run Order Service, invoke its REST API to create an Order,
and check that the HTTP response contains the expected values.
● The drawback of this approach is that not only is the test complex, it’s also slow.
● If these tests were the compile-time tests for the Order class, you’d waste a lot of time
waiting for it to finish.
● A much more productive approach is to write unit tests for the Order class.
Writing Unit Tests For a Service
● The following figure shows, unit tests are the lowest level of the test pyramid.
● They’re technology-facing tests that support development. A unit test verifies that a
unit, which is a very small part of a service, works correctly.
● A unit is typically a class, so the goal of unit testing is to verify that it behaves as
expected.
Writing Unit Tests For a Service
Writing Unit Tests For a Service
There are two types of unit tests:
● Solitary unit test—Tests a class in isolation using mock objects for the class’s
dependencies.
● Sociable unit test—Tests a class and its dependencies.
Writing Unit Tests For a Service
● The responsibilities of the class and its role in the architecture determine which type
of test to use.
● The following figure shows the hexagonal architecture of a typical service and the
type of unit test that you’ll typically use for each kind of class.
● Controller and service classes are often tested using solitary unit tests.
● Domain objects, such as entities and value objects, are typically tested using sociable
unit tests.
Writing Unit Tests For a Service
Writing Unit Tests For a Service
The typical testing strategy for each class is as follows:
● Entities, such as Order, are objects with persistent identity, are tested using sociable
unit tests.
● Value objects, such as Money, are objects that are collections of values, are tested
using sociable unit tests.
● Sagas, such as CreateOrderSaga, maintain data consistency across services, are
tested using sociable unit tests.
● Domain services, such as OrderService,are classes that implement business logic that
doesn’t belong in entities or value objects, are tested using solitary unit tests.
● Controllers, such as OrderController, which handle HTTP requests, are tested using
solitary unit tests.
● Inbound and outbound messaging gateways are tested using solitary unit tests.
Developing Unit Tests for Entities
public class OrderTest {
private ResultWithEvents<Order> createResult;
private Order order;
@Before
public void setUp() throws Exception {
createResult = [Link](CONSUMER_ID, AJANTA_ID,
CHICKEN_VINDALOO
_LINE_ITEMS);
order = [Link];
}
@Test
public void shouldCalculateTotal() {
assertEquals(CHICKEN_VINDALOO_PRICE.multiply(CHICKEN_VINDALOO_QUANTITY),
[Link]());
}
...
Developing Unit Tests for Entities
● The OrderTest class implements the unit tests for the Order entity.
● The class has an @Before setUp() method that creates an Order before running each
test.
● Its @Test methods might further initialize Order, invoke one of its methods, and then
make assertions about the return value and the state of Order.
● The @Test shouldCalculateTotal() method verifies that [Link]() returns
the expected value.
Writing Unit Tests For Value Objects
public class MoneyTest {
private final int M1_AMOUNT = 10;
private final int M2_AMOUNT = 15;
private Money m1 = new Money(M1_AMOUNT);
private Money m2 = new Money(M2_AMOUNT);
@Test
public void shouldAdd() {
assertEquals(new Money(M1_AMOUNT + M2_AMOUNT), [Link](m2));
}
@Test
public void shouldMultiply() {
int multiplier = 12;
assertEquals(new Money(M2_AMOUNT * multiplier),
[Link](multiplier));
}
...
Developing Unit Tests For Sagas
● A saga, such as the CreateOrderSaga class, implements important business logic, so
needs to be tested. It’s a persistent object that sends command messages to saga
participants and processes their replies.
● CreateOrderSaga exchanges command/reply messages with several services, such as
Consumer Service and Kitchen Service.
● A test for this class creates a saga and verifies that it sends the expected sequence of
messages to the saga participants.
● One test you need to write is for the happy path. You must also write tests for the
various scenarios where the saga rolls back because a saga participant sent back a
failure message.
Developing Unit Tests For Sagas
● One approach would be to write tests that use a real database and message broker
along with stubs to simulate the various saga participants.
● For example, a stub for Consumer Service would subscribe to the consumerService
command channel and send back the desired reply message.
● But tests written using this approach would be quite slow.
● A much more effective approach is to write tests that mock those classes that interact
with the database and message broker. That way, we can focus on testing the saga’s
core responsibility.
Developing Unit Tests For Sagas
● The following listing shows a test for CreateOrderSaga. It’s a sociable unit test that
tests the saga class and its dependencies. It’s written using the Eventuate Tram Saga
testing framework.
● This framework provides an easy-to-use DSL that abstracts away the details of
interacting with sagas. With this DSL, you can create a saga and verify that it sends
the correct command messages.
● Under the covers, the Saga testing framework configures the Saga framework with
mocks for the database and messaging infrastructure.
Developing Unit Tests For Sagas
public class CreateOrderSagaTest {
@Test
public void shouldCreateOrder() {
given()
.saga(new CreateOrderSaga(kitchenServiceProxy),
new CreateOrderSagaState(ORDER_ID,CHICKEN_VINDALOO_ORDER))
.expect()
.command(new ValidateOrderByConsumer(CONSUMER_ID, ORDER_ID,
CHICKEN_VINDALOO_ORDER_TOTAL))
.to([Link])
.andGiven()
.successReply()
.expect()
.command(new CreateTicket(AJANTA_ID, ORDER_ID, null))
.to([Link]);
}
Developing Unit Tests For Sagas
@Test
public void shouldRejectOrderDueToConsumerVerificationFailed() {
given()
.saga(new CreateOrderSaga(kitchenServiceProxy),
new CreateOrderSagaState(ORDER_ID, CHICKEN_VINDALOO_ORDER_DETAILS))
.expect()
.command(new ValidateOrderByConsumer(CONSUMER_ID, ORDER_ID,
CHICKEN_VINDALOO_ORDER_TOTAL))
.to([Link])
.andGiven()
.failureReply()
.expect()
.command(new RejectOrderCommand(ORDER_ID))
.to([Link]);
}
}
Developing Unit Tests For Sagas
● The @Test shouldCreateOrder() method tests the happy path.
● The @Test should- RejectOrderDueToConsumerVerificationFailed() method tests the
scenario where Consumer Service rejects the order.
● It verifies that CreateOrderSaga sends a RejectOrderCommand to compensate for the
consumer being rejected.
Writing Unit Tests For Domain Services
● The majority of a service’s business logic is implemented by the entities, value
objects, and sagas.
● Domain service classes, such as the OrderService class, implement the remainder.
● This class is a typical domain service class. Its methods invoke entities and
repositories and publish domain events.
● An effective way to test this kind of class is to use a mostly solitary unit test, which
mocks dependencies such as repositories and messaging classes.
Writing Unit Tests For Domain Services
The following listing shows the OrderServiceTest class, which tests OrderService. It
defines solitary unit tests, which use Mockito mocks for the service’s dependencies. Each
test implements the test phases as follows:
● Setup—Configures the mock objects for the service’s dependencies
● Execute—Invokes a service method
● Verify—Verifies that the value returned by the service method is correct and that the
dependencies have been invoked correctly.
Writing Unit Tests For Domain Services
public class OrderServiceTest {
private OrderService orderService;
private OrderRepository orderRepository;
private DomainEventPublisher eventPublisher;
private RestaurantRepository restaurantRepository;
private SagaManager<CreateOrderSagaState> createOrderSagaManager;
private SagaManager<CancelOrderSagaData> cancelOrderSagaManager;
private SagaManager<ReviseOrderSagaData> reviseOrderSagaManager;
Writing Unit Tests For Domain Services
public class OrderServiceTest {
@Before
public void setup() {
orderRepository = mock([Link]);
eventPublisher = mock([Link]);
restaurantRepository = mock([Link]);
createOrderSagaManager = mock([Link]);
cancelOrderSagaManager = mock([Link]);
reviseOrderSagaManager = mock([Link]);
orderService = new OrderService(orderRepository, eventPublisher,
restaurantRepository, createOrderSagaManager,
cancelOrderSagaManager, reviseOrderSagaManager);
}
Writing Unit Tests For Domain Services
public class OrderServiceTest {
@Test
public void shouldCreateOrder() {
when([Link](AJANTA_ID))
.thenReturn ([Link](AJANTA_RESTAURANT_);
when([Link](any([Link])))
.then(invocation -> {
Order order = (Order) [Link]()[0];
[Link](ORDER_ID);
return order;
});
Writing Unit Tests For Domain Services
public class OrderServiceTest {
@Test
public void shouldCreateOrder() {
Order order = [Link](CONSUMER_ID,
AJANTA_ID, CHICKEN_VINDALOO_MENU_ITEMS_AND_QUANTITIES);
verify(orderRepository).save(same(order));
verify(eventPublisher). publish([Link], ORDER_ID,
singletonList(new
OrderCreatedEvent(CHICKEN_VINDALOO_ORDER_DETAILS)));
verify(createOrderSagaManager)
.create(new CreateOrderSagaState(ORDER_ID,
CHICKEN_VINDALOO_ORDER_DETAILS),[Link], ORDER_ID);
}}
Writing Unit Tests For Domain Services
● The setUp() method creates an OrderService injected with mock dependencies.
● The @Test shouldCreateOrder() method verifies that [Link]()
invokes OrderRepository to save the newly created Order, publishes an OrderCreated-
Event, and creates a CreateOrderSaga.
Developing Unit Tests For Controllers
● Services, such as Order Service, typically have one or more controllers that handle
HTTP requests from other services and the API gateway.
● A controller class consists of a set of request handler methods. Each method
implements a REST API endpoint. A method’s parameters represent values from the
HTTP request, such as path variables.
● It typically invokes a domain service or a repository and returns a response object.
● OrderController, for instance, invokes OrderService and OrderRepository. An effective
testing strategy for controllers is Solitary Unit Tests that mock the services and
repositories.
Developing Unit Tests For Controllers
● You could write a test class similar to the OrderServiceTest class to instantiate a
controller class and invoke its methods. But this approach doesn’t test some
important functionality, such as request routing.
● It’s much more effective to use a mock MVC testing framework, such as Spring Mock
Mvc, which is part of the Spring Framework, or Rest Assured Mock MVC, which builds
on Spring Mock Mvc.
● Tests written using one of these frameworks make what appear to be HTTP requests
and make assertions about HTTP responses.
● These frameworks enable you to test HTTP request routing and conversion of Java
objects to and from JSON without having to make real network calls.
Developing Unit Tests For Controllers
● The following listing shows the OrderControllerTest class, which tests Order Service’s
OrderController. It defines solitary unit tests that use mocks for OrderController’s
dependencies.
● It’s written using Rest Assured Mock MVC , which provides a simple DSL that
abstracts away the details of interacting with controllers. Rest Assured makes it easy
to send a mock HTTP request to a controller and verify the response.
● OrderControllerTest creates a controller that’s injected with Mockito mocks for
OrderService and OrderRepository.
● Each test configures the mocks, makes an HTTP request, verifies that the response is
correct, and possibly verifies that the controller invoked the mocks.
Developing Unit Tests For Controllers
public class OrderControllerTest {
private OrderService orderService;
private OrderRepository orderRepository;
@Before
public void setUp() throws Exception {
orderService = mock([Link]);
orderRepository = mock([Link]);
orderController = new OrderController(orderService, orderRepository);
}
Developing Unit Tests For Controllers
when([Link](1L))
.thenReturn ([Link](CHICKEN_VINDALOO_ORDER_);
given().standaloneSetup(configureControllers(
new OrderController(orderService, orderRepository)))
.when().get("/orders/1")
.then().statusCode(200)
.body("orderId",equalTo(new Long(OrderDetailsMother.ORDER_ID).intValue()))
.body("state",equalTo(OrderDetailsMother.CHICKEN_VINDALOO_ORDER_STATE.name()
))
.body("orderTotal",equalTo(CHICKEN_VINDALOO_ORDER_TOTAL.asString()));
}
@Test
public void shouldFindNotOrder() { ... }
private StandaloneMockMvcBuilder controllers(Object... controllers) {
... }}
Developing Unit Tests For Controllers
● The shouldFindOrder() test method first configures the OrderRepository mock to
return an Order.
● It then makes an HTTP request to retrieve the order.
● Finally, it checks that the request was successful and that the response body contains
the expected data.
Writing Unit Tests For Event And Message Handlers
● Services often process messages sent by external systems.
● Order Service, for example, has OrderEventConsumer, which is a message adapter
that handles domain events published by other services.
● Each of a message adapter’s methods typically invokes a service method with data
from the message or event.
● The following listing shows part of the OrderEventConsumerTest class, which tests
OrderEventConsumer.
Writing Unit Tests For Event And Message Handlers
● It verifies that OrderEventConsumer routes each event to the appropriate handler
method and correctly invokes OrderService. The test uses the Eventuate Tram Mock
Messaging framework, which provides an easy-to-use DSL for writing mock
messaging tests that uses the same given-when-then format as Rest Assured.
● Each test instantiates OrderEventConsumer injected with a mock Order- Service,
publishes a domain event, and verifies that OrderEventConsumer correctly invokes the
service mock.
Writing Unit Tests For Event And Message Handlers
public class OrderEventConsumerTest {
private OrderService orderService;
private OrderEventConsumer orderEventConsumer;
@Before
public void setUp() throws Exception {
orderService = mock([Link]);
orderEventConsumer = new OrderEventConsumer(orderService);
}
Writing Unit Tests For Event And Message Handlers
@Test
public void shouldCreateMenu() {
given().eventHandlers([Link]())
.when().aggregate ("[Link]", AJANTA_ID)
.publishes (new RestaurantCreated(AJANTA_RESTAURANT_NAME,
RestaurantMother.AJANTA_RESTAURANT_MENU))
.then()
.verify(() -> {
verify(orderService)
.createMenu(AJANTA_ID,
new RestaurantMenu(RestaurantMother.AJANTA_RESTAURANT_MENU_ITEMS));
});
}
The Next Post
Integration Test