Swift单元测试:如何Mock协议扩展中的doStuff方法?
doStuff in This Swift Code Structure? Short answer: Not directly with your current setup, but there are straightforward fixes to make it mockable.
Let’s break down why you’re hitting this issue, then walk through actionable solutions.
Why Mocking doStuff Fails Right Now
Your doStuff method comes from a default protocol extension on MyProtocol, and MyClass doesn’t explicitly override it. In Swift, default protocol methods are statically dispatched—meaning when methodToTest calls doStuff(), the compiler directly links to the extension’s implementation, not a dynamically resolvable method on MyClass. This skips any subclass overrides or mock replacements you might try.
Fix 1: Explicitly Implement doStuff in MyClass
The simplest fix is to add an explicit implementation of doStuff to MyClass, even if it just mirrors the default extension. This forces the method to be dynamically dispatched, making it mockable:
final class MyClass: MyProtocol { // Explicitly implement the method (matches the extension's logic) func doStuff(identifier: String) -> Bool { return true } }
Now you can:
- Subclass
MyClass(remove thefinalmodifier if needed) and overridedoStufffor testing - Use a mocking framework like Cuckoo or OCMock to stub
doStuffonMyClassinstances
Fix 2: Dispatch doStuff Through the Protocol Type
If you can’t or don’t want to add an explicit implementation, modify methodToTest to call doStuff on self cast as MyProtocol. This triggers protocol method dispatch, which respects mocked implementations of MyProtocol:
extension MyClass: MyOtherProtocol { public func methodToTest() -> Bool { // Fixed return type to match the protocol // Cast self to MyProtocol to use protocol dispatch if (self as MyProtocol).doStuff(identifier: "test-id") { return doSomething() // Assume doSomething() is defined elsewhere } return false } }
To take this further, refactor to inject a MyProtocol dependency instead of relying on self, making mocking even cleaner:
// Refactor MyOtherProtocol to depend on MyProtocol public protocol MyOtherProtocol: class { var protocolDependency: MyProtocol { get set } func methodToTest() -> Bool } extension MyClass: MyOtherProtocol { public var protocolDependency: MyProtocol { get { return self } set { /* If needed, adjust internal state here */ } } public func methodToTest() -> Bool { if protocolDependency.doStuff(identifier: "test-id") { return doSomething() } return false } } // Manual mock for testing class MockMyProtocol: MyProtocol { var doStuffCalled = false var doStuffReturnValue = true func doStuff(identifier: String) -> Bool { doStuffCalled = true return doStuffReturnValue } var myVar: SomeClass = SomeClass() // Dummy implementation } // Example test case func testMethodToTest_WhenDoStuffReturnsFalse_ReturnsFalse() { let myClass = MyClass() let mockProtocol = MockMyProtocol() mockProtocol.doStuffReturnValue = false myClass.protocolDependency = mockProtocol let result = myClass.methodToTest() XCTAssertTrue(mockProtocol.doStuffCalled) XCTAssertFalse(result) }
Fix 3: Runtime Swizzling (Last Resort)
Tools like OCMock can use Objective-C runtime swizzling to replace even statically dispatched methods, but this is fragile and not recommended for most cases. It relies on implementation details of Swift’s protocol extensions, which could change in future versions.
内容的提问来源于stack exchange,提问作者AnderCover

