swiftxcodetestingmockingprotocols

Protocol conformance issue related to mocking and testing: Argument type 'AMock?' does not conform to expected type 'AProtocol'


I'm building a simple app to practice app architecture, working with protocols, associated types, etc. I think I'm using a pretty common pattern that I've used and seen many times before, however I'm running into this compiler error and I haven't been able to figure out or find the solution yet: Argument type 'AMock' does not conform to expected type 'AProtocol' I'm guessing it's something very basic and embarrassing, however perhaps this will be useful to someone in a similar situation :)

Here's the protocol:

protocol AProtocol {
    func a() -> String
}

I'm selecting both the app and the test targets for it:

target membership

Here's the class conforming to this protocol:

class A: AProtocol {
    func a() -> String {
        return "a"
    } 
}

And here's a class where I'm using this protocol for dependency injection:

class B {
    private let aClass: AProtocol
    
    init(aClass: AProtocol) {
        self.aClass = aClass
    }
    
    func returnString() -> String {
        return aClass.a()
    }
}

So far so good, the code compiles. I'd like to use the above for testing, so I'm setting up a mock:

class AMock: AProtocol {
    func a() -> String {
        return "mock"
    }
}

And adding the following to my XCTest setup:

import XCTest
@testable import ProtocolPractice

final class ProtocolPracticeTests: XCTestCase {
    var sut: B!
    var aMock: AMock!

    override func setUpWithError() throws {
        aMock = AMock()
        sut = B(aClass: aMock) // Argument type 'AMock?' does not conform to expected type 'AProtocol'
    }

    func test() throws {
        XCTAssertEqual(sut.returnString(), "mock")
    }
}

Does anyone know why the compiler thinks that AMock does not conform to AProtocol? To me it seems that it does, and there are no compile errors in the AMock file itself, only in my test file. I've seen this pattern used before and used it myself many times, and I've found examples of it as well, like this one.

Xcode suggests that I Insert ' as! AProtocol' but that doesn't make the issue go away.

I've also tried using var aMock: AProtocol! in the test (in place of var aMock: AMock!) and I get a similar error: Argument type '(any AProtocol)?' does not conform to expected type 'AProtocol'

In case this matters, I'm using Xcode 15.4 with Swift 5.


Solution

  • I'm selecting both the app and the test targets for it:

    This is the basic problem, because it means that AProtocol exists in two modules: ProtocolPractice.AProtocol and ProtocolPracticeTests.AProtocol.

    Your code for AMock therefore references AProtocol from the ProtocolPracticeTests module and not the one from your App.

    This is why the compiler error occurs, because ProtocolPractice.AProtocol and ProtocolPracticeTests.AProtocol are two different protocols.

    The following code would therefore theoretically solve the problem:

    class AMock: ProtocolPractice.AProtocol {
        func a() -> String {
            return "mock"
        }
    }
    

    But you shouldn't do that!

    You should remove AProtocol from the test target. With @testable import ProtocolPractice you get access to the internal declarations of the ProtocolPractice module.

    So for your mock implementations of your test target, you only need to add this import statement.

    @testable import ProtocolPractice
    
    class AMock: AProtocol {
        func a() -> String {
            return "mock"
        }
    }