Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Shouldn't NSUserDefault be clean slate for unit tests?

I'm writing my first iOS unit tests (Xcode 5, iOS 6) and find that the results of the unit tests vary depending on what I've done in the Simulator lately. E.g. I click on a user in my contacts list in the Simulator, and now my "recent contacts" data in UserDefaults has one more object than before, even when I'm running unit tests.

For unit testing, it's not clean to have random user defaults data (I'm used to RoR tests with their own clean db). Besides, I might want to test specific states like having empty "recent contacts" data.

From looking at related questions here, I seem some possible answers that I'm not happy with.

  • Mock UserDefaults for the unit tests! I would have to modify many existing classes so that I can inject that mock.
  • Clear or customize UserDefaults in a setUp method! But then my data created laboriously in manual testing would be gone.
  • Clear or customize UserDefaults in a setUp method then restore those values in tearDown! Ouch.

These seem unnecessarily complicated for something that should be standard practice in unit tests. I don't want to repeat myself in every unit test. So, my questions are:

  • Am I missing something desirable about the way UserDefaults are persisted from ad-hoc Simulator testing through to unit test runs?
  • Is there a configurable way to fix this, say some way to set the unit test target to have different storage location for UserDefaults than when I use the Simulator to manually test?
  • Failing that, is there an elegant way to do this in code?
  • For example, I could have a MyAppTestCase object inherit from XCTestCase and override setUp and tearDown methods to always set aside then restore the UserDefaults. Is this a good idea?
like image 474
LisaD Avatar asked Sep 29 '13 23:09

LisaD


2 Answers

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)     } } 
like image 166
orkoden Avatar answered Oct 01 '22 07:10

orkoden


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.

like image 43
bigkm Avatar answered Oct 01 '22 07:10

bigkm