Mockit - Swift 5.0 中单元测试的美味模拟框架

莫吉特

介绍

MockitSwift 5.0 中单元测试的美味模拟框架。它处于开发的早期阶段,但其当前功能几乎完全可用。

Mockit是一个味道绝妙的嘲讽框架。它允许您使用干净简单的API编写漂亮的测试。使用编写的测试非常可读,并且会产生干净的验证错误。它的灵感来自著名的Java模拟框架 - MockitoMockit

文档

Mockit尚未完整记录,但它附带了一个示例项目,可让您尝试其所有功能并熟悉 API。您可以在 中找到它。Mockit.xcworkspace

有一个名为 的示例测试文件。在此处查找一些可以运行的测试。这将针对模拟协作者测试类。ExampleTests.swiftExampleExampleCollaborator

如果您有任何问题,请提交问题。

若要运行示例项目,请克隆存储库,然后首先从示例目录运行。pod install

局限性

  • 创建模拟需要一些样板代码。有关下面“基本用法”部分中的示例,请参阅。但是,两者都正在进行插件开发,并且每次需要创建模拟时都最大限度地减少编写此样板代码。MockExampleCollaboratorXcodeAppCode

特征

  • Stubbing. Mockit lets you stub a method and then perform any of 3 actions (, , ) individually or in any order via chaining;thenReturnthenDothenAnswer

  • Mocking. You can create a subclass extending the protocol to mock required methods;Mock

  • Call Verification. You can verify method calls using one of 8 supported modes (, , , , , , and OnceAtLeastOnceAtMostOnceTimesAtLeastTimesAtMostTimesNeverOnly);

  • Arguments of specific call. Mockit allows you to obtain the arguments of a specific method call to use custom assertions on them;

  • Helpful messages. If method verification fails or something goes wrong, Mockit provides readable messages that describes the issue;

  • Default Type matchers. Out of the box, Mockit can match the following types:

    • String / String?
    • Bool / Bool?
    • Int / Int?
    • Double / Double?
    • Float / Float?
    • Array / Array? of the above primitive types
    • Dictionary / Dictionary? of the above primitive types

Given that Swift does not have reflection, Mockit cannot magically match your custom types, so you need to subclass protocol to write
your one custom type matcher. For an example, see the Basic Usage section below.
TypeMatcher

Basic Usage

The examples below assume we are mocking this class:

class ExampleCollaborator {

    func voidFunction() {
    }

    func function(int: Int, _ string: String) -> String {
      return ""
    }

    func stringDictFunction(dict: [String: String]) -> String {
      return ""
    }

}
Swift

In your test code, you'll need to create a , which extends and adopts . The mock creates a , and forwards all calls to it:MockExampleCollaboratorExampleCollaboratorMockCallHandler

class MockExampleCollaborator: ExampleCollaborator, Mock {

    let callHandler: CallHandler

    init(testCase: XCTestCase) {
      callHandler = CallHandlerImpl(withTestCase: testCase)
    }

    func instanceType() -> MockExampleCollaborator {
      return self
    }

    override func voidFunction() {
      callHandler.accept(nil, ofFunction: #function, atFile: #file, inLine: #line, withArgs: nil)
    }

    override func function(int: Int, _ string: String) -> String {
      return callHandler.accept("", ofFunction: #function, atFile: #file, inLine: #line, withArgs: int, string) as! String
    }

    override func stringDictFunction(dict: Dictionary<String, String>) -> String {
      return callHandler.accept("", ofFunction: #function, atFile: #file, inLine: #line, withArgs: dict) as! String
    }

}
Swift

To write a custom type matcher:

public class CustomMatcher: TypeMatcher {

    public func match(argument arg: Any, withArgument withArg: Any) -> Bool {
      switch (arg, withArg) {
        case ( _ as CustomType, _ as CustomType):
          // custom matching code here
          return true
        default:
          return false
      }
    }

}
Swift

It is good practice to put the mock objects and custom type matchers in a separate group in the test part of your project.

Currently-supported syntax

// stub a call on a method with parameters, then return value
mockCollaborator.when().call(withReturnValue: mockCollaborator.function(42, "frood")).thenReturn("hoopy")
Swift
// stub a call on a method with dictionary parameter, then answer value
mockCollaborator.when().call(withReturnValue: mockCollaborator.stringDictFunction(["Hello": "Pong"])).thenAnswer {
      (args: [Any?]) -> String in

      // custom code here
    }
Swift
// stub a call on a void method , then do action
mockCollaborator.when().call(withReturnValue: mockCollaborator.voidFunction()).thenDo {
      (args: [Any?]) -> Void in

      // if the call is received, this closure will be executed
      print("===== thenDo closure called =====")
    }
Swift
// stub a call on a method , then return values on multiple calls
mockCollaborator.when().call(withReturnValue: mockCollaborator.stringDictFunction(["Hello": "Pong"])).thenReturn("ping", "hoopy")
Swift
// stub a call on a method , then chain multiple actions for corresponding calls
mockCollaborator.when().call(withReturnValue: mockCollaborator.stringDictFunction(["Hello": "Pong"])).thenReturn("ping", "hoopy").thenAnswer {
      (args: [Any?]) -> String in

      // custom code here
    }
Swift
// call a method and then get arguments of a specific call which can be asserted later
systemUnderTest.doSomethingWithParamters(42, "frood")
systemUnderTest.doSomethingWithParamters(18, "hoopy")

let argumentsOfFirstCall = mockCollaborator.getArgs(callOrder: 1).of(mockCollaborator.function(AnyValue.int, AnyValue.string))
let argumentsOfSecondCall = mockCollaborator.getArgs(callOrder: 2).of(mockCollaborator.function(AnyValue.int, AnyValue.string))
Swift

With nice mocks, Mockit supports the "verify expectations after mocking" style using 8 supported verification modes

// call method on the system under test
systemUnderTest.expectMethodOneAndThree()

// Then
mockCollaborator.verify(verificationMode: Once()).methodOne()
mockCollaborator.verify(verificationMode: Never()).methodTwo()
mockCollaborator.verify(verificationMode: Once()).methodThree()


// call method on the system under test
sut.expectMethodOneTwice()

// Then
mockCollaborator.verify(verificationMode: Times(times: 2)).methodOne()


// call method on the system under test
sut.expectOnlyMethodThree()

// Then
mockCollaborator.verify(verificationMode: Only()).methodThree()


// call method on system under test
sut.expectAllThreeMethods()

// Then
mockCollaborator.verify(verificationMode: Once()).methodOne()
mockCollaborator.verify(verificationMode: AtLeastOnce()).methodTwo()
mockCollaborator.verify(verificationMode: AtMostOnce()).methodThree()


// call method on the system under test
sut.expectMethodTwoAndThree()

// Then
mockCollaborator.verify(verificationMode: AtLeastTimes(times: Times(times: 1))).methodTwo()
mockCollaborator.verify(verificationMode: Never()).methodOne()
mockCollaborator.verify(verificationMode: AtMostTimes(times: Times(times: 3))).methodThree()
Swift

Requirements

  • Xcode 9+
  • XCTest

Installation

Mockit is built with Swift 5.0.

CocoaPods

Mockit is available through CocoaPods. To install
it, simply add the following line to your Podfile:

pod 'Mockit', '1.5.0'
Ruby

Manually

  1. Download and drop folder in your project./Mockit
  2. Congratulations!

Feedback

Issues and pull-requests are most welcome - especially about improving the API further.

Author

Syed Sabir Salman-Al-Musawi, sabirvirtuoso@gmail.com

I'd also like to thank Sharafat Ibn Mollah Mosharraf for his big support during the API design and development phase.

License

Mockit is available under the MIT license. See the file for more info.LICENSE

The PNG at the top of this is from www.mockit.co.uk/about.htmlREADME.md

GitHub

https://github.com/sabirvirtuoso/Mockit