Shouldn't NSUserDefault be clean slate for unit tests?

Using named suites like in this answer worked well for me. Removing the user defaults used for testing could also be done in func tearDown().

class MyTest : XCTestCase {
    var userDefaults: UserDefaults?
    let userDefaultsSuiteName = "TestDefaults"

    override func setUp() {
        super.setUp()
        UserDefaults().removePersistentDomain(forName: userDefaultsSuiteName)
        userDefaults = UserDefaults(suiteName: userDefaultsSuiteName)
    }
}

Available iOS 7 / 10.9

Rather than using the standardUserDefaults you can use a suite name to load your tests

[[NSUserDefaults alloc] initWithSuiteName:@"SomeOtherTests"];

This coupled with some code to remove the SomeOtherTests.plist file from the appropriate directory in setUp will archive the desired result.

You would have to design any objects to take your defaults objects so that there wouldn't be any side effects from the tests.


As @Till suggests, your design is probably incorrect for good testability. Rather than having unit-testable pieces of the system read NSUserDefaults directly, they should work with some other object (which may talk to NSUserDefaults). This is roughly equivalent to "mocking NSUserDefaults", but is really an extra abstraction layer. Your configuration object would abstract both NSUserDefaults and other configuration storage like keychain. It would also ensure that you don't scatter string constants around the program. I've built this kind of config object for many projects and highly recommend it.

Some would argue that unit-testable objects shouldn't rely on singletons like NSUserDefaults or my recommend global "configuration" object at all. Instead, all configuration should be injected at init. In practice, I find this to create too much headache when interacting with Storyboards, but it is worth considering in places where it can be useful.

If you really want to dig deeply into NSUserDefaults, it does provide some layering capability. You may investigate setVolatileDomain:forName: to see if you can create an extra layer for your unit test. In practice, I haven't had much luck with these kinds of things on iOS (more-so on Mac, but still not to the level you would need to trust it).

It is possible to swizzle standardUserDefaults, but I wouldn't recommend this approach if you can avoid it. Your "save everything at start and restore everything at end" is probably the best standardized way to approach the problem if you can't adapt your design to avoid externalities.