The Humble Object Pattern: Simplifying Testable Design

abdul ahad
3 min readJan 27, 2025

--

Photo by Karl Fredrickson on Unsplash

Software development often encounters challenges with testing when dealing with code tightly coupled to external dependencies, frameworks, or hardware. The Humble Object Pattern provides an effective strategy to address these challenges, ensuring code remains testable while maintaining simplicity. This article explores the humble object pattern, its principles, implementation, and real-world applications.

What is the Humble Object Pattern?

The Humble Object Pattern is a design approach that divides a class into two distinct parts:

  1. Humble Object: This is a thin wrapper that handles untestable, trivial, or framework-dependent code. Its primary role is to forward calls without adding complexity.
  2. Logic Class: This contains all the testable logic and complexity, isolated from external dependencies.

The essence of the pattern lies in focusing testing efforts on meaningful application logic while avoiding unnecessary tests for trivial or untestable interactions.

Why Use the Humble Object Pattern?

  • Untestable Code: Certain code interacts with third-party libraries, frameworks, or hardware, making it hard to test in isolation.
  • Simplified Testing: By isolating logic, you can create focused tests without worrying about external dependencies.
  • Reduced Complexity: Keeps untestable code simple, focused, and free of unnecessary logic.

How the Pattern Works

Step 1: Identify Trivial or Untestable Code

Look for code that directly interacts with:

  • Third-party libraries (e.g., logging frameworks or analytics tools).
  • Framework APIs (e.g., UIKit, Android SDK).
  • Hardware or system components (e.g., sensors or file systems).

Step 2: Create a Humble Object

Wrap the untestable or trivial code in a humble object. This class should only forward calls to the external dependency.

Step 3: Move Logic Elsewhere

Place all complex, testable logic in a separate class that interacts with the humble object. This separation ensures that the core application logic is testable in isolation.

Example: Analytics Singleton

Problem

The Analytics class is:

  • A singleton that cannot be mocked, subclassed, or injected.
  • Directly tied to a third-party library, making it difficult to test.

Solution: Applying the Humble Object Pattern

Step 1: Create the Humble Object

The humble object wraps the Analytics class, forwarding calls:

class MyAnalyticsWrapper: AnalyticsWrapper {
func log(event: String) {
Analytics.log(event) // Simple forwarding
}
}

This class does nothing but forward the log method call to the singleton.

Step 2: Create the Logic Class

The logic class contains all testable logic and interacts with the humble object:

class AnalyticsManager {
private let analyticsWrapper: AnalyticsWrapper

init(wrapper: AnalyticsWrapper) {
self.analyticsWrapper = wrapper
}
func trackEvent(event: String) {
// Add testable logic
if !event.isEmpty {
analyticsWrapper.log(event: event)
}
}
}

Testing

You can now test AnalyticsManager by injecting a mock implementation of AnalyticsWrapper:

class MockAnalyticsWrapper: AnalyticsWrapper {
var loggedEvent: String?

func log(event: String) {
loggedEvent = event
}
}
func testTrackEvent() {
let mockWrapper = MockAnalyticsWrapper()
let manager = AnalyticsManager(wrapper: mockWrapper)
manager.trackEvent(event: "TestEvent")
XCTAssertEqual(mockWrapper.loggedEvent, "TestEvent")
}
}

The humble object, MyAnalyticsWrapper, is not tested because it only forwards calls.

When to Use the Humble Object Pattern

  1. External Dependencies: When interacting with APIs, third-party libraries, or hardware.
  2. Trivial Code: When forwarding calls or performing simple transformations.
  3. Legacy Code: When refactoring legacy systems to improve testability.

Benefits of the Humble Object Pattern

  • Improved Testability: By isolating logic, you can focus on testing meaningful parts of your application.
  • Simplified Code: Keeps untestable code simple and free of unnecessary complexity.
  • Decoupled Dependencies: Shields application logic from external dependencies.

Drawbacks

  • Increased Abstraction: Adds an extra layer, which can feel unnecessary in smaller projects.
  • Not Always Needed: For very trivial code, you may decide not to test it instead of creating a humble object.

Key Takeaways

The humble object pattern is a practical design technique for improving testability in software applications. By separating untestable, trivial code into humble objects and isolating logic into testable classes, developers can:

  • Write focused, meaningful tests.
  • Reduce the complexity of tightly coupled code.
  • Make their codebase more maintainable and scalable.

When used properly, the humble object pattern is a powerful tool for creating testable and robust applications.

--

--

abdul ahad
abdul ahad

Written by abdul ahad

A software developer dreaming to reach the top and also passionate about sports and language learning

No responses yet