Mockit
简介
Mockit
是一个 Swift 5.0 的 Tasty 模拟框架。它仍处于开发的早期阶段,但其当前功能几乎完全可用。
Mockit
是一个出色的模拟框架。它允许您使用干净、简单的 API 编写漂亮的测试。使用 Mockit
编写的测试非常易读,并产生干净的验证错误。它受到了著名的 Java 模拟框架——[Mockito](http://mockito.org/) 的启发。
文档
Mockit
尚未完全 documentation,但它附带了一个示例项目,您可以在其中试验所有功能,并熟悉 API。您可以在 Mockit.xcworkspace
中找到它。
有一个名为 ExampleTests.swift
的示例测试文件。在撰写本文时,那里有一些可以运行的测试。这测试了一个名为 Example
的类与模拟的协作者 ExampleCollaborator
。
如果您有任何疑问,请提交问题。
要运行示例项目,首先克隆仓库,然后从 Example 目录运行 pod install
。
限制
- 创建模拟需要一些模板代码。请参见下文“基本用法”部分的
MockExampleCollaborator
示例。然而,正在开发两个插件,一个用于Xcode
,一个用于AppCode
,以最小化每次创建模拟时需要编写的模板代码。
功能
-
存根化。Mockit 允许您存根一个方法,然后通过链式调用执行以下三种操作之一(
thenReturn
、thenDo
、thenAnswer
); -
模拟。您可以通过创建扩展
Mock
协议的子类来模拟所需方法; -
调用验证。您可以使用8种支持的模式之一(
Once
、AtLeastOnce
、AtMostOnce
、Times
、AtLeastTimes
、AtMostTimes
、Never
和Only
)来验证方法调用; -
特定调用的参数。Mockit 允许您获取特定方法调用的参数,以便对它们进行自定义断言;
-
有用的信息。如果方法验证失败或出现问题,Mockit 提供了可读性强的消息,用于描述问题;
-
默认类型匹配器。开箱即用,Mockit 可以匹配以下类型
- String / String?
- Bool / Bool?
- Int / Int?
- Double / Double?
- Float / Float?
- Array / Array? of the above primitive types
- Dictionary / Dictionary? of the above primitive types
由于 Swift 没有反射功能,Mockit 无法神奇地匹配自定义类型,因此您需要子类化TypeMatcher
协议来编写自己的自定义类型匹配器。请参见下文“基本用法”部分的示例。
基本用法
以下示例假设我们正在模拟此类
class ExampleCollaborator {
func voidFunction() {
}
func function(int: Int, _ string: String) -> String {
return ""
}
func stringDictFunction(dict: [String: String]) -> String {
return ""
}
}
在您的测试代码中,您需要一个 MockExampleCollaborator
,它扩展 ExampleCollaborator
并采用 Mock
。该模拟创建一个 CallHandler
,并将所有调用转发到它
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
}
}
编写自定义类型匹配器
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
}
}
}
将模拟对象和自定义类型匹配器放在测试部分的单独组中是很好的实践。
当前支持的语法
// stub a call on a method with parameters, then return value
mockCollaborator.when().call(withReturnValue: mockCollaborator.function(42, "frood")).thenReturn("hoopy")
// 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
}
// 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 =====")
}
// stub a call on a method , then return values on multiple calls
mockCollaborator.when().call(withReturnValue: mockCollaborator.stringDictFunction(["Hello": "Pong"])).thenReturn("ping", "hoopy")
// 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
}
// 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))
通过与美观的模拟器一起,Mockit 支持使用 8 种受支持的验证模式的 “验证模拟后的期望” 风格。
// 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()
需求
- Xcode 9+
- XCTest
安装
Mockit 使用 Swift 5.0 构建。
CocoaPods
Mockit 通过 CocoaPods 提供。要安装它,只需将以下行添加到您的 Podfile 中
pod 'Mockit', '1.5.0'
手动安装
- 下载并将在项目中
/Mockit
文件夹拖拽进来。 - 恭喜!
意见反馈
问题和拉取请求最受欢迎——尤其是关于进一步改进 API 的。
作者
Syed Sabir Salman-Al-Musawi, [email protected]
我还想感谢Sharafat Ibn Mollah Mosharraf在API设计和开发阶段的大力支持。
许可证
Mockit 可在 MIT 许可证 下使用。有关更多信息,请参阅 LICENSE
文件。
此 README.md
顶部的 PNG 图片来自 www.mockit.co.uk/about.html