6

I'm trying to be proactive about writing an iOS app in a test-driven manner. However, I'm stumped as to how to test a method that is to interact with real system files. For those familiar with iOS, I'm writing to the NSUserDefaults file.

Here's my set-up: I have a singleton called AppState that I am using to record when data downloaded from the Internet was last synced locally. Other classes can update the dateLastSynced property of AppState. I am overriding the setDateLastSynced method to save that update to the system so it's persisted between opening and closing the app:

- (void)setDateLastSynced:(NSDate *)dateLastSynced
{
    // update instance variable with new value
    _dateLastSynced = dateLastSynced;

    // store change to NSUserDefaults
    [[NSUserDefaults standardUserDefaults] setObject:dateLastSynced forKey:APP_STATE_DATE_LAST_SYNCED_KEY];
    [[NSUserDefaults standardUserDefaults] synchronize];
}

I'd like to write a test that verifies that setDateLastSynced is working correctly - but I'm not sure how to do that without overwriting data already saved to the system in NSUserDefaults.

  • Could you parameterize [NSUserDefaults standardUserDefaults]? Make it something that can be set by the test, so what you're actually writing to, while testing, is some kind of mock? – Carl Manaster Feb 5 '15 at 23:53
  • I assume the answer involves mocking out NSUserDefaults. I even found an SO user that felt the need to write their own mock for that class. – Ixrec Feb 5 '15 at 23:54
  • So a dependency injection setup where any class wanting to update dateLastSynced had to pass in [NSUserDefaults standardDefaults]. And in my test, pass in a mock NSUserDefaults object? I'll try it – H K Feb 5 '15 at 23:55
  • Neat, I'll check that out too – H K Feb 5 '15 at 23:56
  • You say that you are using TDD. But then, how do you know that your method even needs to interact with system files? In TDD, the tests drive your design. So, either you already have a test that forces you to interact with system files, in which case you already have the answer to your question, or you don't have such a test, but then you cannot know that you need to interact with system files, because the only that you would know that would be if you had a test that told you so. – Jörg W Mittag Feb 6 '15 at 16:49
5

You have to mock or emulate, and you have two very good reasons to do so.

  1. You have to mock or emulate whatever parts of the system your code interacts with, precisely because:

    • You don't want to alter the system, and
    • You don't want your tests to depend on something as uncontrollable as the system.

       Yes, your emulation will not be perfect; yes, you will in fact be modelling your knowledge and assumptions about how the system works; and that's perfectly fine. You are essentially taking a spec and restating it in code: nothing wrong with that. The spec in this case is written by the creators of the operating system and not by you.

  2. You have to mock or emulate everything that deals with time, even your own stuff.

    • You have to be able to set the emulated clock time to a certain fake date, pretend that files are created/modified, and then check their creation/modification date and make sure it is the fake date, and all this without of course changing the system clock.
    • You also have to be able to check that a certain date in the future is correctly set, or that something happens at that moment in time, all during the execution of a single test, without having to wait for real time to advance.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.