一个通过自动化创建模拟对象过程来简化测试编写的软件包。
此软件包提供了一个 Mock 宏,可以应用于协议以生成一种新类型。这种类型的名称将与协议名称相似,并带有 Mock 后缀。
要使用 Mock 类型的对象,需要使用全局的 when(_:)
方法。对于每个协议方法,将生成 2 个方法。一个方法将与协议方法类似,第二个方法将带有 $
符号形式的前缀。当您调用 when(_:)
方法时,您应该使用带有前缀的方法。
when(_:)
方法返回一个 MethodInvocationBuilder
,允许您为 Mock 对象的方法添加桩。在大多数情况下,您应该调用构建器的 MethodInvocationBuilder.thenReturn(_:)
方法,并确定桩应该返回什么值。如果该方法可以抛出错误,那么您可以使用 ThrowsMethodInvocationBuilder.thenThrow(_:)
方法来测试错误处理。
首先,我想澄清一下,目前该软件包仅允许您为仅包含方法且不包含任何属性的公共协议创建 Mock 对象。
为了为您的协议生成模拟类型,您需要在 protocol 关键字之前添加 @Mock
宏。
@Mock
public protocol SomeProtocol {
}
此宏将生成一个新的公共类型,其名称为协议名称,并带有后缀 Mock
。在我们的示例中,类型名称将为 SomeProtocolMock
。
让我们向我们的协议添加 getAlbumName()
方法,该方法返回专辑的名称,类型为 String
。然后让我们继续编写测试。
@Mock
public protocol SomeProtocol {
func getAlbumName() async throws -> String
}
从此刻开始,生成的模拟对象包含桩协议方法的基本功能。要桩方法,我们必须使用 when(_:)
函数。作为函数参数,我们必须指明我们要桩哪个模拟对象的哪个方法。这通过在模拟对象上调用具有相同名称和 $
前缀的生成方法来完成。
func testSomeProtocol() async throws {
let mock = SomeProtocolMock()
when(mock.$getAlbumName())
}
when(_:)
函数返回一个构建器,它允许我们确定如果调用我们的方法应该返回什么。AsyncThrowsMethodInvocationBuilder.thenReturn(_:)
方法用于此目的。
func testSomeProtocol async throws {
let mock = SomeProtocolMock()
when(mock.$getAlbumName())
.thenReturn("I'mperfect")
}
在此之后,每当我们调用 getAlbumName()
方法时,我们将收到我们在 thenReturn(_:)
方法中指定的值。
func testSomeProtocol() async throws {
let mock = SomeProtocolMock()
when(mock.$getAlbumName())
.thenReturn("I'mperfect")
let albumName = try await mock.getAlbumName()
XCTAssertEqual("I'mperfect", albumName)
}
有关更多详细信息,请参阅:桩
当我们桩我们的模拟对象时,最常见的是我们想要检查模拟对象上必要的方法是否已被调用。为此,有 verify(_:times:)
方法,它允许您检查特定方法的调用次数以及调用时使用的参数。
让我们看下面的例子。我们想编写一个测试,检查我们是否为传入的每个专辑 ID 请求了一个名称,并检查我们是否以正确的顺序获得了我们传入模拟对象的相同专辑名称。
让我们想象一下我们服务之上的以下外观模式
final class SomeFacade {
var service: SomeProtocol
init(service: SomeProtocol) {
self.service = service
}
func fetchAlbumNames(_ ids: [String]) async throws -> [String] {
var albums = [String]
albums.reserveCapacity(ids.count)
for id in ids {
try await albums.append(service.getAlbumName(id: id))
}
return albums
}
}
对于此测试,首先,我们必须创建一个模拟对象并使用所有必要的数据桩方法。之后,我们创建我们的外观模式对象并将模拟对象注入到那里。作为最后一步,我们调用我们要测试的外观模式方法,获取数据,检查数据并检查模拟对象调用。
func testFetchAlbum() async throws {
let mock = SomeProtocolMock()
let passedIds = [
"id1",
"id2",
"id3",
"id4",
]
let expected = [
"#4",
"Inspiration Is Dead",
"Just a Moment",
"Still a Sigure Virgin?",
]
for (id, name) in zip(passedIds, expected) {
when(mock.$getAlbumName(id: eq(id)))
.thenReturn(name)
}
let facade = SomeFacade(service: mock)
let actual = try await facade.fetchAlbumNames(passedIds)
XCTAssertEqual(expected, actual)
for id in passedIds {
verify(mock).getAlbumName(id: eq(id))
}
}
有关更多详细信息,请参阅验证