Mocking UserDefaults in Swift
So first, as CAD97 rightly points out, if you simply define your protocol as identically matching the methods from UserDefaults you intend to use, then making UserDefaults conform to the protocol is much simpler:

extension UserDefaults: UserDefaultsProtocol {}


And there ya go, done.  Having to write less code is nice.  What's even nicer here is that we're doing all of this presumably for the sake of testing.  If you're testing right, you're probably looking at your code coverage numbers.  Using this approach, we don't have rogue uncovered lines in the extension that we're not going to hit because we're using the mock.



I'm not a huge fan of the name you've picked for the protocol.  Maybe it's not so bad... and it does largely depend on what you're actually using user defaults for.  But more generally, user defaults is just a key-value store for persisting information between sessions.  There are other key-value store options as well though.  And I believe you'd be better served by making your protocol more generally match more of the options.  The advantage gained here is that swapping from user defaults to key chain to any other system because pretty effortless.



I do not like the UserDefaultsController at all.  You've added an extra, unnecessary layer of complications here.  It's a singleton god class... and it means that if you set up your whole app today to use user defaults, the option to change just parts of it over to a different option aren't as easy.

There's a better way.

Your current approach looks like this:

func fooBarFunc() -&gt; String? {
    return UserDefaultsController.shared.object(forKey: "fooBarKey") as? String
}


However, we could alternatively do something more like this...

func fooBarFunc(from storage: UserDefaultsProtocol = UserDefaults.standard) -&gt; String? {
    return storage.object(forKey: "fooBarKey") as? String
}


Both of these are still called the same:

let value = fooBarFunc()


Because in the second implementation, we pass UserDefaults.standard as the default argument for the storage to pull from.  However, when we want to test this method, we create a different object and pass that in:

let mockStorage = MockStorage(withValue: "expectedValue")
let value = fooBarFunc(from: mockStorage)
XCTAssertEqual(value, "expectedValue")


And alternative, the storage could be pass in to an object's constructor:

init(storage: UserDefaultsProtocol = UserDefaults.standard)


In the main target, we don't pass anything for this argument and just let it default to the standard user defaults.  But again in tests, we implement a mock class that conforms to the protocol and initialize our class with that mock.



This is called dependency injection.  Instead of relying on a god class living somewhere in our code base (and increasing the coupling of our whole app), we pass dependencies into the code that relies on these dependencies.  We use a protocol to keep our code base tightly aligned, but passing objects in, we keep coupling relatively low.  If one class suddenly starts needing something a little different, we can just start passing it something different instead of having to touch the code in the god class... which every other corner of our code base touches... potentially introducing tons of regressions.