The AnyMockable macro allows you to semi-automatically create a mock for any protocol.
- Manually create a class, actor, or struct manually and add it to the desired protocol.
- Add a macro, methods, or properties to the created declaration. It automatically generates a mock nested class based on the implemented protocol and adds an object of this nested class.
To implement the methods, manually enable proxying of these methods for an object of the mock class. Example:
protocol ParentService {
func checkStatus() async throws -> Bool
}
protocol IService: AnyActor, ParentService {
var delegate: Delegate? { get async }
var parent: ParentService { get async }
func upload(file: Data) async throws
}
@AnyMockable
actor IServiceMock: IService {
weak var delegate: Delegate?
@MockAccessor // Сгенерировано макросом
var parent: ParentService
func upload(file: Data) async throws {
try await mock.upload(file: file)
}
func checkStatus() async throws -> Bool {
try await mock.checkStatus()
}
// Сгенерировано макросом
internal let mock = Mock()
internal final class Mock {
var underlyingParent: ParentService!
private let lock = AtomicLock()
// MARK: - upload
fileprivate func upload(file: Data) async throws {
uploadFileCallsCount += 1
uploadFileReceivedArguments.append(file)
if let uploadFileError {
throw uploadFileError
}
try await uploadFileClosure?(file)
}
var uploadFileCallsCount = 0
var uploadFileReceivedArguments: [Data] = []
var uploadFileError: Error?
var uploadFileClosure: ((Data) async throws -> Void)?
// MARK: - checkStatus
fileprivate func checkStatus() async throws -> Bool {
checkStatusCallsCount += 1
if let checkStatusError {
throw checkStatusError
}
if let checkStatusClosure {
return try await checkStatusClosure()
} else {
return checkStatusReturnValue
}
}
var checkStatusCallsCount = 0
var checkStatusError: Error?
var checkStatusClosure: (() async throws -> Bool )?
var checkStatusReturnValue: Bool!
}
}
// Generated by macro
extension IServiceMock: ProxyableMock { }
Using the created mock:
func test() {
let mock = IServiceMock()
...
XCTAssertEqual(mock.checkStatusCallsCount, 1)
XCTAssertNil(mock.checkStatusError)
}
The MockAccessor auxiliary macro is automatically added to each non-optional property in the mock declaration and uses a getter and setter to proxy a similar underlying property from the internal mock class.
@MockAccessor var parent: ParentService
This macro is expanded as follows:
var parent: ParentService {
get {
mock.underlyingParent
}
set(newValue) {
mock.underlyingParent = newValue
}
}
Don't use the @MockAccessor macro manually. It's automatically added to each non-optional property of the mock declaration.