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– Carl Manaster2015年02月05日 23:53:56 +00:00Commented Feb 5, 2015 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– Ixrec2015年02月05日 23:54:48 +00:00Commented Feb 5, 2015 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 itH K– H K2015年02月05日 23:55:57 +00:00Commented Feb 5, 2015 at 23:55
-
Neat, I'll check that out tooH K– H K2015年02月05日 23:56:29 +00:00Commented Feb 5, 2015 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– Jörg W Mittag2015年02月06日 16:49:56 +00:00Commented Feb 6, 2015 at 16:49
1 Answer 1
You have to mock or emulate, and you have two very good reasons to do so.
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.
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.
Explore related questions
See similar questions with these tags.