Bending Time in Spring Applications
Tags: java, spring, beginners, programming
Published: 2023-05-08 15:37:38

Did you ever feel the need to unleash your inner Doctor Strange and manipulate time concisely when testing your Spring application?

Let's check out the newest addition to the Spring ecosystem!

## Spring Modulith

_Spring Modulith supports developers implementing logical modules in Spring Boot applications. It allows them to apply structural validation, document the module arrangement, run integration tests for individual modules, observe the modules' interaction at runtime and generally implement module interaction in a loosely-coupled way._
_[Documentation](https://docs.spring.io/spring-modulith/docs/0.6.0/reference/html/#fundamentals)_


In this article we will focus on one specific area of Modulith, the **Moments** API.

## Moments

The Moments API enables developers to easily react to the passage of time-based events in your application. 

This allows you to easily write code which should be executed once a day, once a week etc.

## Example application

I created a small sample application demonstrating features of the Moments API.  
The code can be found on [Github](https://github.com/tobHai/spring-modulith-samples).   
Let's walk through the application code together.

### Setup
First we need to add the Modulith dependencies to our Spring application. 
I'm using Maven to manage the dependencies in the sample application:

```
<dependencyManagement>
	<dependencies>
		<dependency>
			<groupId>org.springframework.experimental</groupId>
			<artifactId>spring-modulith-bom</artifactId>
			<version>0.6.0</version>
			<type>pom</type>
			<scope>import</scope>
		</dependency>
	</dependencies>
</dependencyManagement>
```

and the actual dependency:

```
<dependency>
	<groupId>org.springframework.experimental</groupId>
	<artifactId>spring-modulith-moments</artifactId>
</dependency>
```

Of course, we also want to test our application code, so we will add the test dependency as well:

```
<dependency>
	<groupId>org.springframework.experimental</groupId>
	<artifactId>spring-modulith-starter-test</artifactId>
	<scope>test</scope>
</dependency>
```

### Daily notifications

In the example application we want to send out notifications daily to our customers. 
At the end of each day, we want to fetch all our customers and check if they opted in for receiving a notification on this day.

Let's create a simple Spring service called `CustomerNotificationService`.

To listen to events emitted by Moments, we annotate a public method with `@EventListener` and add the event we want to listen to as a method parameter:

```
@EventListener
public void on(final DayHasPassed dayHasPassed) {}
```

Now we can easily implement our business logic and let Moments take care of the rest:

```
@EventListener
public void on(final DayHasPassed dayHasPassed) {
  var passedDate = dayHasPassed.getDate();
  log.info("{} has passed. Checking notifications for customers.", passedDate);
  for (var customer : customerService.getCustomers()) {
    if (customer.allowedNotificationDays().contains(passedDate.getDayOfWeek())) {
      eventPublisher.publishEvent(new CustomerNotificationEvent(this, customer.id(), passedDate.getDayOfWeek()));
    }
  }
}
```
For simplicity and brevity reasons the `CustomerService` returns two hard-coded customers:  


```
@Service
public class CustomerService {

  public Collection<Customer> getCustomers() {
    return List.of(new Customer(1, Set.of(DayOfWeek.MONDAY, DayOfWeek.FRIDAY)),
        new Customer(2, Set.of(DayOfWeek.SATURDAY, DayOfWeek.SUNDAY)));
  }
}
```

If the customer opted in for receiving notifications on this day, we simply emit a new `CustomerNotificationEvent` which could then be picked up by some other service and actual send out the notification. 

Since we care about our code, we want to ensure that it is working properly. Thankfully Modulith comes with great test support. 
Time for some time bending :)

### Testing time

The Moments API exposes a class called `TimeMachine`. 
With it, we can easily manipulate time in our tests. 
In the following test we will enable the `TimeMachine` by setting `spring.modulith.moments.enable-time-machine=true`.  

Additionally, we will annotate the test class with `@ApplicationModuleTest`. This is another cool feature provided by Modulith. 
With it only beans defined in the given module (in our case `customer`) will be created upon test execution. 


```
@ApplicationModuleTest
@Import(CustomerNotificationServiceTestConfig.class)
@TestPropertySource(properties = "spring.modulith.moments.enable-time-machine=true")
class CustomerNotificationServiceTest {

    private final TimeMachine timeMachine;

    CustomerNotificationServiceTest(final TimeMachine timeMachine) {
        this.timeMachine = timeMachine;
    }

    @Test
    void sendNotificationToCustomer(final PublishedEvents publishedEvents) {
        for (var i = 0; i < 7; i++) {
            timeMachine.shiftBy(Duration.ofDays(1));
        }

        assertThat(publishedEvents.ofType(CustomerNotificationEvent.class)
                .matching(e -> e.getCustomerId() == 1))
                .hasSize(2)
                .extracting(CustomerNotificationEvent::getDayOfWeek)
                .containsOnly(DayOfWeek.MONDAY, DayOfWeek.FRIDAY);

        assertThat(publishedEvents.ofType(CustomerNotificationEvent.class)
                .matching(e -> e.getCustomerId() == 2))
                .hasSize(2)
                .extracting(CustomerNotificationEvent::getDayOfWeek)
                .containsOnly(DayOfWeek.SATURDAY, DayOfWeek.SUNDAY)
        ;
    }
}
```
#### Bootstrapping with ApplicationModuleTest

If we run the test and check the logs we can see what `@ApplicationModuleTest` does under the hood:

```
Bootstrapping @org.springframework.modulith.test.ApplicationModuleTest for Customer in mode STANDALONE (class dev.tobhai.modulithevents.ModulithEventsApplication)…

# Customer
> Logical name: customer
> Base package: dev.tobhai.modulithevents.customer
> Direct module dependencies: none
> Spring beans:
  + ….CustomerNotificationService
  + ….CustomerService
```

With the help of `@ApplicationModuleTest` we can easily test the interaction between multiple beans defined in a module without the need for a full-blown Spring Context. 

#### Shifting time in the test
Now to the time-bending part of the test:
First we define a fixed `Clock` instance in the `CustomerNotificationServiceTestConfig`.
Then we enable the `TimeMachine` by setting: `spring.modulith.moments.enable-time-machine` to `true`.

Now we can simply inject the `TimeMachine` instance into our test class and use it to shift time around in a very readable and concise way:
`timeMachine.shiftBy(Duration.ofDays(1));`

#### Assertions on emitted events

How can we actually verify that the `CustomerNotificationEvent` was emitted properly?

Modulith helps us out in this scenario as well:
By adding `PublishedEvents publishedEvents` to the signature of our test method, we can access all emitted events and perform assertions on them.

`publishedEvents.ofType(CustomerNotificationEvent.class)
                .matching(e -> e.getCustomerId() == 1)` helps us to access all `CustomerNotificationEvent`s for customer with ID. 
The time in our test starts on a Monday, and we shift "time" by seven days. 
Therefore, we expect that two events (for Monday and Friday) are emitted for customer 1.

## Wrap up

In this article we explored the Moments API of the Spring Modulith projects. 
Additionally, we had a look at testing support provided Modulith.

Did you play around with Modulith yet? 
If so, what is your favorite feature?

Feel free to follow for more Java related content :)


Cover photo by Aron Visuals on [Unsplash](https://unsplash.com/photos/BXOXnQ26B7o)