Skip to content

An implementation of the "Unusual Spending" kata using James Shore's Nullable Infrastructure pattern

Notifications You must be signed in to change notification settings

mgrandrath/nullable_infrastructure_kata_1

Repository files navigation

Nullable Infrastructure Kata

Introduction

This is an implementation of the Unusual Spending kata using James Shore's Nullable Infrastructure pattern. The Nullable Infrastructure pattern is an alternative approach to using mock objects inside tests.

The basic idea is that objects that cause side effects have a kind of an "off switch". With this we can turn off any interaction with the outside world that these objects normally have. That way we can use the real objects as collaborators in our tests and don't have to replace them with mock objects or other test doubles. An object with this kind of switch is called a "Nullable" and in its off-state it is called a null-object. (This by the way has nothing to do with the notion of null or a null-pointer.)

We strictly separate all side effects (or "infrastructure") from the core domain (or business) logic which is completely free of side effects or in other words "pure". Side effects are all interactions with state that is outside of our system. They include access to global state like date/time or random number generators, reading and writing files on disk, network communication, database queries, third party APIs, and so on.

This separation is at the heart of various architectural patterns like for example the Hexagonal Architecture which lends itself very well to the Nullable Infrastructure pattern. In a nutshell Hexagonal Architecture defines a domain core which is pure. This core then interacts with the outside world via ports that are then connected to adapaters that implement the concrete side effects. Using Nullable Infrastructure for implementing these adapters is a perfect fit.

The "Unusual Spending" kata

The objective of the Unusual Spending kata is to implement a system that (given a customer ID) fetches two lists of payments a single customer made. One of the current month and one of the previous month. In both lists the payment amounts have to be summed up by payment category. If there is an increase of more than 50% in a category from the previous month to the current month then this is regarded an unusual spending. If one or more unusual spendings are detected then the system should send an email to the customer in question, informing them about the potential issue.

The pure domain logic in this case is the code that sums up the amounts by category and compares them. The infrastructure consists of a calendar (to get the current date), an API to load the lists of payments, and an email service that can send an email by talking to an SMTP server. The UI part is out of the scope of this kata. All infrastructure classes implement an interface (or port) that is defined by the application layer. This architecture prevents the domain code from depending on implementation details and also makes it easier to test.

Hexagon

The Nullable Infrastructure pattern

When testing the domain code that interacts with the infrastructure we want to avoid triggering side effects for a couple of reasons. Side effects make our tests slow because they have to wait for file or even network I/O. Our tests become harder to set up because we have to take care of external dependencies like files, APIs or databases. Also, our tests become dependent on this external state as well. Whether they succeed or fail depends on the state these other systems are in.

For this reason why we implement our domain logic in a way so that we can pass the infrastructure implementations in. This pattern is called dependency injection because we pass in (or "inject") the dependencies our code needs. It enables us to pass in implementations of the infrastructure interfaces that we have full control over and that we can configure to behave exactly the way we need them to in each individual test.

One popular approach to provide such controlled infrastructure objects is to use so-called "mock objects" or "mocks". These are objects that implement the same interfaces as our infrastructure but are completely separate from our own production implementations. Instead of triggering a side effect a mock records the methods that have been called on it and the arguments that have been passed. The test then uses this information to assert that the tested code invoked the expected methods with the correct data. They are usually created using a mocking library or framework that automatically creates mock objects for a given interface. A significant downside of this approach is that tests that use mocks focus on testing the interactions between the tested code and the injected dependency instead of the resulting behavior. In general this leads to tests that are tightly coupled to implementation internals and that tend to hinder refactoring.

The second approach is to use "fakes". These are implementations of our infrastructure interfaces that are specifically designed to be used inside tests. For example we might use an in-memory database implementation instead of the real one. Unless we already have such implementations for other reasons this strategy causes a sigificant amount of maintenance effort. Also, the behavior of the fake implementation needs to match the real one very closely.

Nullables offer a different option to this problem. As described above Nullable objects provide an "off switch" that allows us to use the actual production implementations but with their side effect short-circuited. This keeps our tests fast and independent of external state. It also avoids the need for third party mocking frameworks that can get quite complex. Additionally it results in stable tests that don't break easily when the code is refactored. This encourages the practice of continuous design.

The Nullable Infrastructure implementation

It demonstrates the use of

  • Configurable responses
  • Output tracking
  • Parameterless instantiation
  • Embedded stubs
  • Narrow sociable tests
  • State-based tests
  • Zero-impact instantiation (?)
  • Narrow integration tests (?)
  • Paranoic telemetry
  • Nullables

Class diagram

About

An implementation of the "Unusual Spending" kata using James Shore's Nullable Infrastructure pattern

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published