Testing Mastery
InMemoryKeyValueStore Pattern
March 29, 2026
5 min read

An in-memory store gives you complete isolation between tests—no cleanup required.
Follow along with the code: iOS-Practice on GitHub
The InMemoryKeyValueStore
class InMemoryKeyValueStore: KeyValueStore {
private var storage: [String: Any] = [:]
func bool(forKey key: String) -> Bool {
storage[key] as? Bool ?? false
}
func string(forKey key: String) -> String? {
storage[key] as? String
}
func integer(forKey key: String) -> Int {
storage[key] as? Int ?? 0
}
func setBool(_ value: Bool, forKey key: String) {
storage[key] = value
}
func setString(_ value: String?, forKey key: String) {
if let value = value {
storage[key] = value
} else {
storage.removeValue(forKey: key)
}
}
func setInt(_ value: Int, forKey key: String) {
storage[key] = value
}
func removeObject(forKey key: String) {
storage.removeValue(forKey: key)
}
// Helper for tests
func reset() {
storage.removeAll()
}
}
Clean, Isolated Tests
class OnboardingManagerTests: XCTestCase {
var store: InMemoryKeyValueStore!
var sut: OnboardingManager!
override func setUp() {
store = InMemoryKeyValueStore()
sut = OnboardingManager(store: store)
}
override func tearDown() {
store.reset()
}
func test_shouldShowOnboarding_onFirstLaunch_returnsTrue() {
// Fresh store = first launch
XCTAssertTrue(sut.shouldShowOnboarding())
}
func test_shouldShowOnboarding_afterCompletion_returnsFalse() {
sut.completeOnboarding()
XCTAssertFalse(sut.shouldShowOnboarding())
}
}
Testing Version Upgrades
func test_shouldShowWhatsNew_onFirstInstall_returnsFalse() {
// No previous version = first install, show onboarding instead
XCTAssertFalse(sut.shouldShowWhatsNew(currentVersion: "1.0.0"))
}
func test_shouldShowWhatsNew_whenVersionChanged_returnsTrue() {
sut.updateVersion(to: "1.0.0")
XCTAssertTrue(sut.shouldShowWhatsNew(currentVersion: "2.0.0"))
}
func test_shouldShowWhatsNew_whenVersionSame_returnsFalse() {
sut.updateVersion(to: "1.0.0")
XCTAssertFalse(sut.shouldShowWhatsNew(currentVersion: "1.0.0"))
}
Testing Launch Count Logic
func test_shouldShowRatePrompt_atLaunchCount10_returnsTrue() {
store.setInt(10, forKey: "launchCount")
XCTAssertTrue(sut.shouldShowRatePrompt())
}
func test_shouldShowRatePrompt_atLaunchCount5_returnsFalse() {
// 5 % 10 != 0, so no prompt
store.setInt(5, forKey: "launchCount")
XCTAssertFalse(sut.shouldShowRatePrompt())
}
func test_incrementLaunchCount_incrementsFromZero() {
sut.incrementLaunchCount()
XCTAssertEqual(sut.launchCount, 1)
}
Benefits
| Before (UserDefaults.standard) | After (InMemoryKeyValueStore) |
|---|---|
| Tests pollute each other | Each test gets fresh state |
| Tests affect real app data | Completely isolated |
| Need manual cleanup | Reset in tearDown |
| Order-dependent failures | Run in any order |
| Can't test "first launch" | Always starts empty |
The in-memory store is fast, isolated, and requires no cleanup between test runs.