Testing Mastery

DateProviding: Control Time in Tests

March 23, 2026
5 min read
Featured image for blog post: DateProviding: Control Time in Tests

Now let's build the mock that makes time-based tests deterministic.

Follow along with the code: iOS-Practice on GitHub

The MockDateProvider

class MockDateProvider: DateProviding {
    var now: Date

    init(now: Date = Date()) {
        self.now = now
    }

    // Helper to create dates easily
    func setNow(year: Int, month: Int, day: Int, hour: Int = 12) {
        var components = DateComponents()
        components.year = year
        components.month = month
        components.day = day
        components.hour = hour
        now = Calendar.current.date(from: components)!
    }
}

Testing Subscription Expiration

class SubscriptionManagerTests: XCTestCase {
    var mockDateProvider: MockDateProvider!
    var sut: SubscriptionManager!

    override func setUp() {
        mockDateProvider = MockDateProvider()
        sut = SubscriptionManager(dateProvider: mockDateProvider)
    }

    func test_isSubscriptionValid_whenNotExpired_returnsTrue() {
        // Arrange: It's June 1st
        mockDateProvider.setNow(year: 2024, month: 6, day: 1)
        let subscription = makeSubscription(
            expiresYear: 2024, expiresMonth: 12, expiresDay: 31
        )

        // Act
        let isValid = sut.isSubscriptionValid(subscription)

        // Assert
        XCTAssertTrue(isValid)
    }

    func test_isSubscriptionValid_whenExpired_returnsFalse() {
        // Arrange: It's June 1st, subscription expired Jan 1st
        mockDateProvider.setNow(year: 2024, month: 6, day: 1)
        let subscription = makeSubscription(
            expiresYear: 2024, expiresMonth: 1, expiresDay: 1
        )

        // Act
        let isValid = sut.isSubscriptionValid(subscription)

        // Assert
        XCTAssertFalse(isValid)
    }
}

Testing the 7-Day Reminder

func test_shouldShowRenewalReminder_whenExpiresIn7Days_returnsTrue() {
    // Arrange: June 1st, expires June 8th = 7 days
    mockDateProvider.setNow(year: 2024, month: 6, day: 1)
    let subscription = makeSubscription(
        expiresYear: 2024, expiresMonth: 6, expiresDay: 8
    )

    // Act & Assert
    XCTAssertTrue(sut.shouldShowRenewalReminder(subscription))
}

func test_shouldShowRenewalReminder_whenExpiresIn8Days_returnsFalse() {
    // Arrange: June 1st, expires June 9th = 8 days (too early)
    mockDateProvider.setNow(year: 2024, month: 6, day: 1)
    let subscription = makeSubscription(
        expiresYear: 2024, expiresMonth: 6, expiresDay: 9
    )

    // Act & Assert
    XCTAssertFalse(sut.shouldShowRenewalReminder(subscription))
}

func test_shouldShowRenewalReminder_whenExpiresToday_returnsFalse() {
    // Arrange: Already expired (0 days left)
    mockDateProvider.setNow(year: 2024, month: 6, day: 1)
    let subscription = makeSubscription(
        expiresYear: 2024, expiresMonth: 6, expiresDay: 1
    )

    // Act & Assert
    XCTAssertFalse(sut.shouldShowRenewalReminder(subscription))
}

Test Helper

func makeSubscription(
    expiresYear: Int,
    expiresMonth: Int,
    expiresDay: Int
) -> Subscription {
    var components = DateComponents()
    components.year = expiresYear
    components.month = expiresMonth
    components.day = expiresDay
    components.hour = 23
    components.minute = 59
    let expirationDate = Calendar.current.date(from: components)!

    return Subscription(
        id: "test_sub",
        planName: "Pro",
        expirationDate: expirationDate,
        createdAt: Date().addingTimeInterval(-86400 * 30)
    )
}

Key Insight

With MockDateProvider, you can test any point in time:

  • Expired yesterday
  • Expires today
  • Expires in exactly 7 days
  • Leap year boundaries
  • Year transitions

The tests run the same way every time, regardless of when they execute.

Originally published on pixelper.com

© 2026 Christopher Moore / Dead Pixel Studio

Let's work together

Professional discovery, design, and complete technical coverage for your ideas

Get in touch