如何模拟协议的通用实现?
How to mock a generic implementation of a protocol?
我正在尝试进行一些依赖注入以进行单元测试,同时也使 iOS 实时预览更容易。
我有一个 Store
协议,我想在我的 SUT classes 中使用它们,这样我就可以通过模拟实现。
protocol Store {
associatedtype AnyData
func load() -> AnyData?
func save(data anyData: AnyData)
}
class FileStore<AnyData: Codable>: Store {
func load() -> AnyData? { /* Actual code that saves to a file */ }
func save(data anyData: AnyData) { /* Actual code that saves to a file */ }
}
class MockStore<AnyData: Codable>: Store {
func load() -> AnyData? { /* Returns mocked value for unit testing */ }
func save(data anyData: AnyData) { /* Returns mocked value for unit testing */ }
}
但是,在我的 SUT class 中,我有以下内容:
class MyClassToBeTested {
// THIS DOESN'T WORK
let bookStore: Store // ERROR: Protocol 'Store' can only be used as a generic
// constraint because it has Self or associated type requirements
// DOESN'T WORK EITHER
let studentStore: Store<Student> // ERROR: Cannot specialize non-generic type 'Store'
}
// in real app
MyClassToBeTested(
bookStore: FileStore<Book>()
studentStore: FileStore<Student>()
)
// in test or preview
MyClassToBeTested(
bookStore: MockStore<Book>()
studentStore: MockStore<Student>()
)
好像我卡住了。我本质上是在尝试拥有一个通用协议,类似于 Java 中的通用接口。我错过了什么?
更新
按照@Jessy 的回答,我做了:
class MyClassToBeTested<BookStore: Store, StudentStore: Store>
where
BookStore.AnyData == Book,
StudentStore.AnyData == Student
{
let bookStore: BookStore
let studentStore: StudentStore
这样就解决了一半的问题。另一半是如何用它键入变量:
class OtherClass {
var sut: MyClassToBeTested // ERROR: Generic parameter Bookstore could not be inferred
var sut2: MyClassToBeTested< // I know this isn't supported in Swift
Store<Book>, // but can't figure out the right syntax
Store<Student> // ERROR: Cannot specialize non-generic type 'Store'
> // ERROR: Protocol 'Store' as a type cannot conform to the protocol itself
var sut3: MyClassToBeTested< // Compiles, BUT I cannot pass a
FileStore<Book>, // MockStore in my tests/preview
FileStore<Student> // so it's useless
>
}
从未支持 Store<Student>
语法,但最近 Swift 论坛上有很多关于它的讨论——可能很快就会支持。
Stack Overflow 上还有许多其他 Q/A 的内容是关于无法使用具有关联类型(如存在)的协议——它终于可用,但仅在 Xcode 13.3(测试版)。
在我看来,你想要这样的约束:
class MyClassToBeTested<BookStore: Store, StudentStore: Store>
where
BookStore.AnyData == Book,
StudentStore.AnyData == Student
{
let bookStore: BookStore
let studentStore: StudentStore
如果不是,从 Xcode 13.3 开始可用的最新语法是
class MyClassToBeTested<StudentStore: Store>
where StudentStore.AnyData == Student {
let bookStore: any Store
let studentStore: StudentStore
init(
bookStore: any Store,
studentStore: StudentStore
) {
self.bookStore = bookStore
self.studentStore = studentStore
}
}
我正在尝试进行一些依赖注入以进行单元测试,同时也使 iOS 实时预览更容易。
我有一个 Store
协议,我想在我的 SUT classes 中使用它们,这样我就可以通过模拟实现。
protocol Store {
associatedtype AnyData
func load() -> AnyData?
func save(data anyData: AnyData)
}
class FileStore<AnyData: Codable>: Store {
func load() -> AnyData? { /* Actual code that saves to a file */ }
func save(data anyData: AnyData) { /* Actual code that saves to a file */ }
}
class MockStore<AnyData: Codable>: Store {
func load() -> AnyData? { /* Returns mocked value for unit testing */ }
func save(data anyData: AnyData) { /* Returns mocked value for unit testing */ }
}
但是,在我的 SUT class 中,我有以下内容:
class MyClassToBeTested {
// THIS DOESN'T WORK
let bookStore: Store // ERROR: Protocol 'Store' can only be used as a generic
// constraint because it has Self or associated type requirements
// DOESN'T WORK EITHER
let studentStore: Store<Student> // ERROR: Cannot specialize non-generic type 'Store'
}
// in real app
MyClassToBeTested(
bookStore: FileStore<Book>()
studentStore: FileStore<Student>()
)
// in test or preview
MyClassToBeTested(
bookStore: MockStore<Book>()
studentStore: MockStore<Student>()
)
好像我卡住了。我本质上是在尝试拥有一个通用协议,类似于 Java 中的通用接口。我错过了什么?
更新
按照@Jessy 的回答,我做了:
class MyClassToBeTested<BookStore: Store, StudentStore: Store>
where
BookStore.AnyData == Book,
StudentStore.AnyData == Student
{
let bookStore: BookStore
let studentStore: StudentStore
这样就解决了一半的问题。另一半是如何用它键入变量:
class OtherClass {
var sut: MyClassToBeTested // ERROR: Generic parameter Bookstore could not be inferred
var sut2: MyClassToBeTested< // I know this isn't supported in Swift
Store<Book>, // but can't figure out the right syntax
Store<Student> // ERROR: Cannot specialize non-generic type 'Store'
> // ERROR: Protocol 'Store' as a type cannot conform to the protocol itself
var sut3: MyClassToBeTested< // Compiles, BUT I cannot pass a
FileStore<Book>, // MockStore in my tests/preview
FileStore<Student> // so it's useless
>
}
从未支持
Store<Student>
语法,但最近 Swift 论坛上有很多关于它的讨论——可能很快就会支持。Stack Overflow 上还有许多其他 Q/A 的内容是关于无法使用具有关联类型(如存在)的协议——它终于可用,但仅在 Xcode 13.3(测试版)。
在我看来,你想要这样的约束:
class MyClassToBeTested<BookStore: Store, StudentStore: Store>
where
BookStore.AnyData == Book,
StudentStore.AnyData == Student
{
let bookStore: BookStore
let studentStore: StudentStore
如果不是,从 Xcode 13.3 开始可用的最新语法是
class MyClassToBeTested<StudentStore: Store>
where StudentStore.AnyData == Student {
let bookStore: any Store
let studentStore: StudentStore
init(
bookStore: any Store,
studentStore: StudentStore
) {
self.bookStore = bookStore
self.studentStore = studentStore
}
}