I recently wrote about Connascence. I continue to find it a fascinating topic, and I recently realized that I could use it to evaluate design decisions.

Several months ago, I released Double Agents, a test double library for Smalltalk. The biggest design decision I had to make was what the client API should look like. From what I’ve seen, there are two main approaches to test double library APIs.

The first and most common is a “builder-style” API. In Ruby, flexmock and rspec-mocks use this style of API. In Smalltalk, SmallMock also uses a builder-style API. In rspec-mocks, a method expectation is set like this:

Mock Expectation in rspec-mocks
logger = double("Logger")
logger.should_receive(:account_closed).with(account)

In a builder-style API, multiple messages are sent to the test double in order to configure it completely, often using method chaining. A direct translation of this API to Smalltalk would be quite ugly because method chaining requires lots of nested parentheses:

Ugly rspec-mock API in Smalltalk
logger := TestDouble named: 'Logger'.
(logger shouldReceive: #accountClosed) with: account.

It would be relatively easy to clean this up by using cascading messages instead:

Nicer rspec-mock API in Smalltalk
logger := TestDouble named: 'Logger'.
logger shouldReceive: #accountClosed;
with: account.

The alternative to a builder-style API is a direct API where a single message send does all of the configuration necessary:

Direct API in Ruby
logger = double("Logger")
logger.should_receive(:account_closed, account)
Direct API in Smalltalk
logger := TestDouble named: 'Logger'.
logger shouldReceive: #accountClosed with: account.

There are pluses and minuses to both approaches. The direct API can result in a combinatorial explosion of methods as more configuration options are added to the library. This is especially true in Smalltalk where there is much less flexibility in specifying arguments to methods.

The builder API is more flexible, but requires the library implementation to hold some internal state in order to allow subsequent messages to make further modifications to the current test double. It also needs to find a way to “close out” the current test double once configuration is complete.

For Double Agents, I chose to use a direct API because I felt it resulted in client code that was more compact and readable than the alternative. I don’t want my tests dominated by the test double configuration, and a direct API in Smalltalk helps with that. But I definitely pay the method explosion cost.

As I started learning and thinking about Connascence, I realized that I could also think about this API choice in terms of Connascence. A builder-style API can often result in Connascence of Execution (CoE) because the some of the messages must be sent in a particular order. For example, it doesn’t make much sense to write:

Reversed Mock Expectation in rspec-mocks
logger = double("Logger")
logger.with(account).should_receive(:account_closed)

even if it might actually work properly.

A direct API results only in the weaker Connascence of Name (CoN).

Connascence isn’t the only tool to use when evaluating a design, but it can definitely inform your decisions.