Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to Unit Test method that calls NSDate()?

I'm currently using Swift.

My question is simple, how do you unit test a method that create an object in a database with NSDate() as a property?

The problem is that on my assert I'll create a new NSDate() like this:

let expectedObject = Object(data: NSDate())
XCTAssertEqual(database.objects(), [expectedObject])

And the assert will fail because the 2 NSDates are slightly different.

Thank you.

like image 957
Rodrigo Ruiz Avatar asked Mar 06 '15 07:03

Rodrigo Ruiz


3 Answers

A good way to test methods that use Date() (or CLLocationManager, ReachabilityManager, or anything else static), is to set up an environment for your entire app which can be mocked in tests.

To do this, create an Environment class like so:

struct Environment {

    var date: () -> Date = Date.init
}

var Env = Environment()

Throughout the rest of your app, access the current date via Env.date() instead of calling Date().

In your tests, create a mock environment like this:

extension Environment {

    static let mock = Environment(
        date: {
            return Date(year: 2019, month: 11, day: 12, hour: 2, minute: 45, second: 0, millisecond: 0, timeZone: TimeZone.init(abbreviation: "CST"))
        }
    )
}

And in the setup for your tests, replace the app environment with the mock env like this:

override func setUp() {

    super.setUp()

    Env = .mock
}

I'd recommend putting that in a base class that all your test classes will inherit from so every test will use the mock environment.

Now when you run your app you will see the current date, but in tests the date will always come back as Nov. 12, 2019 02:45, so you can verify whatever you want from that known date.

For a working example of this setup, I've created a little demo project here. Environment.swift, MockEnvironment.swift, DateExamplesViewController.swift, and DateTests.swift are the relevant files.

like image 149
BevTheDev Avatar answered Oct 09 '22 02:10

BevTheDev


As pointed out in https://testdriven-ios.com

a very good approach is to override the NSDate.date in the test case forcing it to return a known date

@implementation NSDate (custom)

+ (instancetype)date{
    NSDateFormatter* formatter = NSDateFormatter.new;
    [formatter setDateFormat:@"yyyy-MM-dd HH:mm:ssZZZ"];
    return [formatter dateFromString:@"2017-12-28 13:00:10+0000"];
}

@end

Then everywhere where NSDate.date is called, the 2017-12-28 13:00:10+0000 will be returned

@implementation NSDate (custom)

+ (instancetype)date{
    NSDateFormatter* formatter = NSDateFormatter.new;
    [formatter setDateFormat:@"yyyy-MM-dd HH:mm:ssZZZ"];
    return [formatter dateFromString:theDate];
}

@end

you can even customize it further by using a static date variable and then modify it in the test

-(void) test_something_out {
 theDate = @"2017-12-28 13:00:10+0000";
 //Test any function that uses nsdate.date
}
like image 33
Jordi Puigdellívol Avatar answered Oct 09 '22 01:10

Jordi Puigdellívol


If you can use Date.now() like this, it will be very easy and clean to implement

extension Date {
    static var now: (()->Date) = {
        return Date()
    }
}

print(Date.now())
Date.now = { Date().addingTimeInterval(43534543) }
print(Date.now())
like image 44
ChanOnly123 Avatar answered Oct 09 '22 01:10

ChanOnly123