Test Doubles Part 2 : Understanding Dummies

abdul ahad
4 min readJan 9, 2025

--

Photo by Etienne Girardet on Unsplash

Dummy objects are passed around but never actually used. Usually they are just used to fill parameter lists. “Martin Fowler”

When writing tests, especially unit tests, it is important to focus on the behavior being tested. Sometimes, the system under test (SUT) relies on components or objects that are not relevant to the test scenario. To handle these, we use dummy objects and dummy values as placeholders to set up the system without affecting the test outcome. Let’s look at these concepts in detail with examples in Swift.

What Are Dummy Values?

Dummy values are simple placeholders used to replace fields or inputs that are not relevant to the test. Unlike dummy objects, these are typically used for primitive types like String, Int, or Bool.

Example: Using Dummy Values

Suppose we have a method that calculates a discount for a user based on their membership level. If we only care about testing the discount logic, we can use dummy values for unrelated fields.

struct User {
let name: String
let age: Int
let city: String
let membershipLevel: Int
}

func calculateDiscount(for user: User) -> Double {
switch user.membershipLevel {
case 1: return 0.05
case 2: return 0.10
case 3: return 0.20
default: return 0.0
}
}

Test with Dummy Values

In this test, only the membershipLevel field is relevant:

func testCalculateDiscount_ForMembershipLevel3_ShouldReturn20Percent() {
// Arrange
let user = User(name: "Dummy", age: 0, city: "Dummy", membershipLevel: 3)
// Act
let discount = calculateDiscount(for: user)
// Assert
XCTAssertEqual(discount, 0.20, "Discount for level 3 should be 20%")
}

Here, name, age, and city are dummy values that have no impact on the test.

What Are Dummy Objects?

A dummy object is a stand-in object used to meet a dependency without being actively utilized in the test. The main purpose of a dummy object is to:

  1. Fulfill the dependencies needed to initialize the system.
  2. Show clearly that the dependency is irrelevant to the current test.

Example: Using Dummy Object

We have a UserService that depends on a Logger. However, the logger isn't used in the validateUser method, so we can use a dummy object to satisfy this dependency.

Step 1: Define Dependencies

1. Protocol for the Logger

The Logger is a dependency of UserService. We'll define it as a protocol:

protocol Logger {
func log(message: String)
}

2. User Model

The User model represents the user being validated:

struct User {
let name: String
let age: Int
let city: String
}

3. User Validator

A simple utility for validating users:

struct UserValidator {
static func validate(user: User) -> Bool {
return !user.name.isEmpty && user.age > 0 && !user.city.isEmpty
}
}

Step 2: Create the UserService

The UserService depends on the Logger but doesn't use it in validateUser.

class UserManager {
private let logger: Logger

init(logger: Logger) {
self.logger = logger
}

func validateUser(_ user: User) -> Bool {
return UserValidator.validate(user: user)
}

// Other methods that might use the logger
func log(message: String) {
logger.log(message: message)
}
}

Step 3: Create the Dummy Logger

The dummy logger satisfies the dependency but does nothing in its implementation.

class DummyLogger: Logger {
func log(message: String) {
// Do nothing
}
}

Step 4: Write the Test

Here’s how you can write a test for the validateUser method using the dummy logger.

import XCTest

class UserManagerTests: XCTestCase {
func testValidateUser_WhenAllFieldsAreValid_ShouldReturnTrue() {
// Arrange
let user = User(name: "abdul", age: 29, city: "karachi")
let dummyLogger = DummyLogger()
let userManager = UserManager(logger: dummyLogger)

// Act
let isUserValid = userManager.validateUser(user)

// Assert
XCTAssertTrue(isUserValid, "User is valid when all fields are completed")
}
}

Key Points in the Test

  1. Dependency Injection: We inject the DummyLogger into the UserService. This allows us to satisfy the Logger dependency without having to provide a functional implementation.
  2. Focus on Behavior: The test focuses solely on the behavior of the validateUser method, without being affected by the Logger dependency.
  3. Dummy Logger: The DummyLogger makes it explicit that the logger is not used in this context, ensuring clarity and intent.

Conclusion

Dummy objects and dummy values are essential tools for effective and focused testing. By using these placeholders, you can keep your tests clean, simple, and meaningful. Whether you’re satisfying a complex dependency with a dummy object or replacing irrelevant fields with dummy values, the key is to ensure your tests are easy to understand and maintain while accurately verifying the behavior of your code.

References:

--

--

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