lundi 30 mai 2016

Mocking dependencies that use protocol extensions does not call the mock method

I have an issue where I am trying to test a class that has two dependencies. The dependencies implement a protocol, and I pass two 'mock' objects that also implement the protocol(s) to my object under test. I have reproduced the problem in a small test app below.

import UIKit

// first dependency of `Data` (below)
protocol Local {}
extension Local {
    // the default method that I want my app to use when running a
    // `normal` execution mode, i.e. not a test
    func isCached (url : NSURL) -> Bool {
        return true
    }
}

// the actual class definition that the app normally runs
class LocalImpl : Local {}

// much the same as `Local` above
protocol Server {}
extension Server {
    func getURL (url : NSURL) -> NSData? {
        // todo
        return nil
    }
}
class ServerImpl : Server {}

// The object that I want to test.
protocol Data {
    var local : Local { get set }
    var server : Server { get set }
}
extension Data {

    func getData (url : NSURL) -> NSData? {
        if local.isCached(url) {
            return nil
        } else {
            return server.getURL(url)
        }
    }
}
class DataImpl : Data {
    var local : Local
    var server : Server

    init () {
        local = LocalImpl()
        server = ServerImpl()
    }

    init (server : Server, local : Local) {
        self.server = server
        self.local = local
    }
}


class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.

        let data : Data = DataImpl()
        data.getData(NSURL(string: "http://google.com")!)
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
}

Then, in my test

@testable import TestingTests

class Test: XCTestCase {

    // the mock server instance I want to use in my test
    class MockServer : Server {
        static var wasCalled = false
        func getURL(url: NSURL) -> NSData? {
            MockServer.wasCalled = true
            return nil
        }
    }

    // the local instance I want to use in my test (with overridden protocol function)
    class MockLocal : Local {
        func isCached (url : NSURL) -> Bool {
            return false
        }
    }

    func testExample() {
        let data = DataImpl(server: MockServer(), local: MockLocal())
        data.getData(NSURL(string: "http://hi.com")!)
        XCTAssert(MockServer.wasCalled == true)
    }
}

The above test will fail. When stepping through the test with a debugger, the protocol definition of isCached will be called on the Local object. In other words, the "default" implementation runs instead of the one that I defined in my test.

If I set breakpoints in my test file, the Data object is set up properly and has my mocks set. However, once I step into the getData function, trying to print out data.local or data.server from LLDB will produce bad access errors (app doesn't actually crash, but the debugger cannot print the value)

Am I missing something here, or can someone explain to me why you cannot do this?

Running Xcode 7.3.1 with Swift 2

Aucun commentaire:

Enregistrer un commentaire