Ever since reading Alex Eagle’s article Change-Detector Tests Considered Harmful, whenever I write I a unit test, I ask myself, “is this a change detector?” It’s a useful reflex to have, but I think Eagle’s article wasn’t completely clear on what it means for a unit test to be a change detector. In this article, I’ll provide concrete examples delineating the difference between change detectors and good unit tests.

# Unit Tests Test Value

The change detector concept was better articulated later, by Lukas Atkinson in this StackOverflow answer. Atkinson makes several important points.

1. The purpose of a unit test is to test the value which the unit under test adds to the application.
2. There is value in using an abstract parts combinator.
3. Looking at Eagle’s unit test example, we don’t know if the `Processor` class is an abstract parts combinator.

## An Abstract Whatinator?

“Combinator” is a math term (and a functional programming term). Its technical meaning is irrelevant for this discussion. In this article, think of it as a component which has subcomponents which are usable elsewhere.

## Breaking Down the Example

Let’s rewrite Eagle’s unit test in Java. It would look something like this.

And here is a minimal implementation of the implied classes.

Per Atkinson, the reason that we can’t tell if the `testProcessing` unit test is worthwhile is that we don’t know what this code is doing.

Atkinson provides two cases, one to demonstrate how the `testProcessing` unit test would be a useless change detector, and one to demonstrate how it would be a good unit test.

### Change Detector Case: Woeful Interest Calculator

The example Atkinson uses to demonstrate how the `testProcessing` unit test would be a change detector is an interest calculator in which the calculation is split into two parts.

To make this concrete, here is a very basic implementation of said interest calculator which is structurally identical to the `Processor` class above. (Ignore the fact that you’d never write an interest calculator this way, mutating some object via `void` methods.)

Notice that, as Atkinson points out, if the interest calculation is wrong, this test still passes.

This implementation is so horrendous that the best you could likely do with unit testing it is to make sure that both halves of the calculation actually happened (and then test the implementations elsewhere), but that’s mostly irrelevant. The point is, given how `AccruedInterestCalculator` works, the `testProcessing` test tests no added value and breaks when the implementation changes. It is a change detector.

#### The Responsibilities of the Unit

As an aside, if `AccruedInterestCalculator` were doing anything beyond what its subcomponents do (for example, applying an early withdrawal penalty rate after the other calculations), there would be something of substance to test.

This is what Atkinson means when he talks about the unit under test having a “more concrete responsibility within the problem domain”.

With our unfortunate implementation (which mutates values via `void` methods), you’d have to test that penalty with a fake instead of a mock.

### Good Unit Test Case: Event Handler

The example which I will use to demonstrate how the `testProcessing` unit test would be a good unit test is a late payment event handler.

Though `LatePaymentEventHandler` is structurally identical to the original `Processor` class, the `testProcessing` unit test is actually useful for `LatePaymentEventHandler` because `LatePaymentEventHandler` is a combinator: its subcomponents, `CreditCardCharger` and a `ReceiptEmailer`, are useful elsewhere, and its value is in the way it combines them. This is Atkinson’s second case.

The value-add here is that the `CreditCardCharger` is used before the `ReceiptEmailer`, presumably throwing an error if something goes wrong and preventing an email from being sent out if the customer wasn’t actually charged. In this case, the subcomponent order does matter and so testing the subcomponent order is worthwhile.

## Lesson

The structure of the unit under test isn’t enough to determine whether or not a unit test is a change detector. You have to understand the work it is doing and how that adds value to the application, and test that.