How to drown Swift "Trait / Mixin" method in struct / class for testing

I recently read about how to add “Traits / Mixins” to a structure / class in Swift, creating a protocol and distributing that protocol with a default implementation. This is great, as it allows me to add functionality to view the controller without adding a bunch of helper objects to the specified view controller. My question is: how do I block calls that these default implementations provide?

Here is a simple example:

protocol CodeCop {
  func shouldAllowExecution() -> Bool
}

extension CodeCop {
  func shouldAllowExecution() -> Bool {
    return arc4random_uniform(2) == 0
  }
}

struct Worker : CodeCop {
  func doSomeStuff() -> String {
    if shouldAllowExecution() {
       return "Cop allowed it"
     } else {
       return "Cop said no"
    }
  }
}

, , "Cop allowed it" doStuff(), CodeCop , , , "Cop said no" by doStuff(), CodeCop .

+4
2

, , CodeCopStub, CodeCop:

protocol CodeCopStub: CodeCop {
    // CodeCopStub declares a static value on the implementing type
    // that you can use to control what is returned by
    // `shouldAllowExecution()`.
    //
    // Note that this has to be static, because you can't add stored instance
    // variables in extensions.
    static var allowed: Bool { get }
}

CodeCopStub shouldAllowExecution(), CodeCop, allowed. CodeCop , CodeCopStub.

extension CodeCopStub {
    func shouldAllowExecution() -> Bool {
        // We use `Self` here to refer to the implementing type (`Worker` in
        // this case).
        return Self.allowed
    }
}

, , - Worker CodeCopStub:

extension Worker: CodeCopStub {
    // It doesn't matter what the initial value of this variable is, because
    // you're going to set it in every test, but it has to have one because
    // it static.
    static var allowed: Bool = false
}

:

func testAllowed() {
    // Create the worker.
    let worker = Worker()
    // Because `Worker` has been extended to conform to `CodeCopStub`, it will
    // have this static property. Set it to true to cause
    // `shouldAllowExecution()` to return `true`.
    Worker.allowed = true

    // Call the method and get the result.
    let actualResult = worker.doSomeStuff()
    // Make sure the result was correct.
    let expectedResult = "Cop allowed it"
    XCTAssertEqual(expectedResult, actualResult)
}

func testNotAllowed() {
    // Same stuff as last time...
    let worker = Worker()
    // ...but you tell it not to allow it.
    Worker.allowed = false

    let actualResult = worker.doSomeStuff()
    // This time, the expected result is different.
    let expectedResult = "Cop said no"
    XCTAssertEqual(expectedResult, actualResult)
}

, , . , , .

+2

, , , :

  • CodeCop ( CodeCop.swift) (CodeCop + shouldAllowExecution.swift)

  • CodeCop.swift , , CodeCop+shouldAllowExecution.swift .

  • CodeCopTest.swift, , shouldAllowExecution, .

CodeCopTest.swift

import XCTest

fileprivate var shouldCopAllowExecution: Bool = false

fileprivate extension CodeCop {

    func shouldAllowExecution() -> Bool {
        return shouldCopAllowExecution
    }
}

class PeopleListDataProviderTests: XCTestCase {

    var codeCop: CodeCop!

    override func setUp() {
        super.setUp()
        codeCop = CodeCop()
    }

    override func tearDown() {
        codeCop = nil
        super.tearDown()
    }

    func testWhenCopAllows() {
        shouldCopAllowExecution = true
        XCTAssertEqual(codeCop.doSomeStuff(), "Cop allowed it", "Cop should say 'Cop allowed it' when he allows execution")
    }

    func testWhenCopDenies() {
        shouldCopAllowExecution = false
        XCTAssertEqual(codeCop.doSomeStuff(), "Cop said no", "Cop should say 'Cop said no' when he does not allow execution")
    }
}
+2

Source: https://habr.com/ru/post/1692105/


All Articles