Deterministic unit tests for current date-dependent code in Swift.
Tags: swift, ios, codingtips, testing
Published: 2018-10-13 22:26:25

---
title: Deterministic unit tests for current date-dependent code in Swift.
published: true
description: A simple trick to make writing code that's dependent on the current date (calls `Date()`) perfectly and easily unit-testable, meaning that unit-test are deterministic (not dependent on the current date) and easy-to-maintain.
tags: swift, iOS, coding-tips, testing
---

_It's been a while since I published my previous article on [Envelope framework](https://dev.to/ivanmisuno/envelopenetwork-or-how-to-abstract-alamofire-and-provide-strongly-typed-mock-responses-in-unit-tests--rxswift-bindings-22m2) - a thin wrapper around Alamofire that makes writing unit tests for the networking code a piece of cake. Probably it was too big of an article to start with, but anyway I'd like to hear more feedback on in. Today I'm going to share a very simple tip I've been using since a few years ago that simplifies another aspect of writing unit-tests: testing the code that uses current date/time._



# The problem

So imagine you're writing a method that requests an update for a record if it has expired:
```swift
final class Controller {
    private let entityManager: EntityManaging

    func requestUpdateIfNeeded(_ record: Record) {
        let dateOfExpiration = Date().addingTimeInterval(
            -configuration.expirationInterval)
        if record.lastUpdateTime <= dateOfExpiration {
            entityManager.requestUpdate(record.id)
        }
    }
}
```

Here, `entityManager` is an object responsible for requesting and storing an update to the record by `id`.


How could a unit test for such a function look like?
```swift
import Quick
import Nimble
@testable import MainAppModule

class ControllerSpec: QuickSpec {
    override func spec() {
        var sut: Controller!
        var entityManager: EntityManagingMock!
        beforeEach {
            // Construct instances of `sut` and `entityManager`
        }
        describe("Controller.requestUpdateIfNeeded()") {
            context("when expired") {
                let expiredRecord = RecordFixture.expiredRecord()
                beforeEach {
                    sut.requestUpdateIfNeeded(expiredRecord)
                }
                it("requests entityManager to update the record by its id") {
                    expect(entityManager.requestUpdateCallCount) == 1
                }
            }
            context("when not expired") {
                let nonExpiredRecord = RecordFixture.nonExpiredRecord()
                beforeEach {
                    sut.requestUpdateIfNeeded(nonExpiredRecord)
                }
                it("does not request update") {
                    expect(entityManager.requestUpdateCallCount) == 0
                }
            }
        }
    }
}
```

See the problem? We need a `nonExpiredRecord`, i.e., an instance of `Record` for which the condition `record.lastUpdateTime <= dateOfExpiration` would be `false`. So the `RecordFixture.nonExpiredRecord()` should generate a `Record` with `lastUpdateTime` updated to the current time! Imagine how this would look like when `Record` is a `struct` - you'll have to copy all fields of the struct, updating one with the current date. Even worse, when such a fixture comes from e.g., a saved network response, the schema of which could change over time, supporting such test code becomes a constant pain, and a source of failures on the CI. Even if the fixture is constructed properly, stepping over the function in the debugger could result in evaluating the condition to the wrong result if the actual time has already passed.

# The solution

_Freeze the time._


/Captain obvious mode on/ Unit-tests should be deterministic.
Even the ones that deal with the current time. /Captain obvious mode off/

Imagine that calling `Date()` when running under unit-test suite would always return, say, `1 January 2016 12:00GMT`? Then creating a test fixture for a record that's always "unexpired" would be trivial, isn't it?

So how we could override `Date()` under unit-tests so that it would return a predefined date? For the good or for the worse, that's not possible directly - swizzling a method implementation is now a thing of the past.

What we can do, is to:

1. Provide an alternative to `Date()` that would under normal program execution return current date, with the ability for the unit-test suite to override its behavior;
1. In the test suite, override it to always return a pre-defined date;
1. Prohibit usage of `Date()` initializer in the source code with a lint rule, or a git pre-commit hook, or both.


Let's do this step by step.

## 1. Providing an alternative to getting the current date.

```swift
//  Date+current.swift

internal var __date_currentImpl: () -> Date {
    return Date()
}

extension Date {
    /// Return current date.
    /// Please replace `Date()` and `Date(timeIntervalSinceNow:)` with `Date.current`,
    /// the former will be prohibited by lint rules/commit hook.
    static var current: Date {
        return __date_currentImpl()
    }
}

```

## 2. Overriding the current date behavior under the test suite.

```swift
//  Date+mockCurrentDate.swift

@testable import MainAppModule
import Quick

// `configure()` function gets executed when test suite is loaded (same rules as +[NSObject load] apply);
/// This replaces the `Date.current` implementation so that when running under the test suite it always returns `Date.mockDate`,
/// allowing to write unit-tests than test the code dependent on the current date.
class MockCurrentDateConfiguration: QuickConfiguration {
    override class func configure(_ configuration: Configuration) {
        // This gets executes before initialization of `let` constants in unit tests.
        Date.overrideCurrentDate(Date.mockDate)

        // This gets executed before any other `beforeEach` blocks.
        configuration.beforeEach {
            Date.overrideCurrentDate(Date.mockDate)
        }

        // Restore the possibly overridden (in a Quick test) mock date handler.
        configuration.afterEach {
            Date.overrideCurrentDate(Date.mockDate)
        }
    }
}

extension Date {
    static var mockDate: Date = Date(timeIntervalSinceReferenceDate: 536500800) // 1 January 2018, 12:00GMT

    static func overrideCurrentDate(_ currentDate: @autoclosure @escaping () -> Date) {
        __date_current = currentDate
    }
}

```

If `Quick` is not being used, then the same trick could be done by overriding one of the unit-test suite classes' `class func load()` functions.


So now, the code in the main app module that calls `Date.current`, when running under test suite, would always receive the value of `Date.mockDate`, so constructing test fixtures becomes as easy as:
```swift
    let expiredRecord = Record(
        lastUpdateTime: Date.mockDate.addingTimeInterval(
            -configuration.expirationInterval)
    let nonExpiredRecord = Record(
        lastUpdateTime: Date.mockDate)
```

Alternatively, the test case could override the value of the current date for the code:
```swift
    // ...
    beforeEach {
        // Override the current date
        Date.overrideCurrentDate(Date.mockDate.add)
    }
```


## 3. Prohibiting the usage of `Date()` in the code.

Let's add a section to the git pre-commit hook script, with a simple regexp to find all occurrences of `Date()` pattern being added to the stage area:

```shell
#!/bin/sh
# pre-commit
# copy to .git/hooks folder ()

date=$(git diff --cached -- *.swift ":(exclude)*/Date+current.swift" | grep -E '\bDate\(\)\b' | wc -l)
if [ "$date" -gt 0 ] ; then
    echo "  Error: Usage of Date() initializer is prohibited."
    echo "         Please use Date.current value, and make sure unit-tests are not dependent on the actual current date."
    exit 1
fi
```

Hope this makes sense. Happy hacking!

Also, would really appreciate any feedback.

Thanks!