Hermetic Unit Testing

Mathew Phillip Wheatley
Geek Culture
Published in
2 min readApr 2, 2021
Photo by CDC on Unsplash

A large part of programming is writing tests to make sure your code works as expected. On smaller projects writing tests can seem cumbersome but they will show their value as features are added, modules are refactored, and the team grows. Most software engineers are familiar with the basic testing types such as unit tests, integration tests, and acceptance tests. Today I wanted to talk about keeping unit tests hermetic to ensure they remain unit tests instead of becoming integration tests.

Hermetic is often associated with clean room laboratory but in the world of software it refers to tests which are insulated or protected from outside influences. These influences could be dependancies such as a database, library, API, or anything else which is not the specific set of code being tested. A unit test by design shall have a narrow scope which focuses on testing a single objective of the system. For instance, only testing a single method of a class. This narrow scope makes unit tests easy to understand and troubleshoot as well as keeping them fast and reliable.

For instance, consider the Figure 1, which takes in a hash from an external API as an argument. This hash containing a temperature in Celsius which to_fahrenheit converts to Fahrenheit.

def to_fahrenheit(api_return)
(api_return[:temperature] * 9/5) +32
end
Figure 1: Arbitrary Ruby method to coverting Celsius to Fahrenheit

Since this code is expecting a return from an external API it is natural to think a unit test should make a call to the external API before passing it to to_fahrenheit. Although this works it’s not really a unit test, it is an integration test due to the external API call. The external call will add time and complexity to the test which could make this straight forward method flaky. Instead, the return of the external API should be subbed as shown in Figure 2 to keep the unit test hermetic.

class TestMeme < Minitest::Test
def setup
@fake_api_return = {temperature: 100}
end

def test_to_fahrenheit
assert_equal 212, to_fahrenheit(@fake_api_return)
end
end
Figure 2: Hermetic Unit Test of to_fahrenheit using Ruby minitest

This was a simple example but it is a concept you should keep in mind when writing your unit tests. Are you making an unnecessary call to the database or a library to test a method? Is this test failing because of the code you wrote or the service you are using is down? That being said, unit tests will not cover all your testing needs, some integration tests and acceptance tests are likely required. In the above example an integration test should be added to check the return of the external API to make sure it’s the shape you expect. Overall, you are writing the tests, make them work for you, not against you.

--

--

Mathew Phillip Wheatley
Geek Culture

I am a software engineer with a mechanical background. Interests swing from aerospace, to woodworking, travel, skiing, hiking, running, climbing, and lasers.