Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor Verify structure generation for support different containers #3

Merged
merged 1 commit into from
Oct 16, 2023
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Refactor Verify structure generation for support different containers
MetalheadSanya committed Oct 16, 2023
commit c2da69efb95a870e249456d4d0a8267c37cf8940
4 changes: 4 additions & 0 deletions Sources/SwiftMock/Documentation.docc/Usage/Verifying.md
Original file line number Diff line number Diff line change
@@ -31,6 +31,8 @@ func test() {

By default, verify checks that the method was called exactly one time. If the method was called more than once, you will receive an error.

> Important: The framework only checks calls that have not yet been checked. If you repeat the method check a second time, you will receive an error that the call was not found.

### Checking that property was read

To verify that the mock property has been read, use the ``verify(_:times:)`` method and pass the mock object as the first argument. This method will create a `Verify` structure for you that has a method to verify that your property has been read. The name of the method is `propertyGetter()`, where `property` is the name of your property. All rules regarding method call verification work similarly for properties.
@@ -134,3 +136,5 @@ func test() {
verify(mock, times: atLeast(2)).getAlbumName()
}
```

> Important: Using ``atLeast(_:)`` and ``atMost(_:)`` will mark all calls that match the passed ``ArgumentMatcher``s as verified.
2 changes: 1 addition & 1 deletion Sources/SwiftMock/Functions.swift
Original file line number Diff line number Diff line change
@@ -177,5 +177,5 @@ public func when<Arguments, Result>(
}

public func verify<Mock: Verifiable>(_ mock: Mock, times: @escaping TimesMatcher = times(1)) -> Mock.Verify {
Mock.Verify(mock: mock, times: times)
Mock.Verify(mock: mock, container: mock.container, times: times)
}
1 change: 0 additions & 1 deletion Sources/SwiftMock/TimesMatcher/And.swift

This file was deleted.

18 changes: 18 additions & 0 deletions Sources/SwiftMock/Verify/Container.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
//
// File.swift
//
//
// Created by Alexandr Zalutskiy on 13/10/2023.
//

public protocol CallContainer {

func append<T>(mock: AnyObject, call: MethodCall<T>, function: String)
func verify<T>(
mock: AnyObject,
matcher: ArgumentMatcher<T>,
times: TimesMatcher,
type: String,
function: String
)
}
2 changes: 1 addition & 1 deletion Sources/SwiftMock/Verify/MethodCall.swift
Original file line number Diff line number Diff line change
@@ -10,7 +10,7 @@ public struct MethodCall<Arguments> {
matcher match: ArgumentMatcher<Arguments>,
times: TimesMatcher,
type: String,
function: String = #function
function: String
) {
let callCount = container.filter { match($0.arguments) }.count
guard times(callCount) else {
4 changes: 3 additions & 1 deletion Sources/SwiftMock/Verify/Verifiable.swift
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
public protocol MockVerify {
associatedtype Mock
init(mock: Mock, times: @escaping TimesMatcher)
init(mock: Mock, container: CallContainer, times: @escaping TimesMatcher)
}

public protocol Verifiable {
associatedtype Verify: MockVerify where Verify.Mock == Self

var container: VerifyContainer { get }
}
56 changes: 56 additions & 0 deletions Sources/SwiftMock/Verify/VerifyContainer.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
//
// MethodCallContainer.swift
//
//
// Created by Alexandr Zalutskiy on 12/10/2023.
//

import Foundation

public class VerifyContainer: CallContainer {
var calls: [Any] = []
var functions: [String] = []
var isVerified: [Bool] = []

public init() { }

public func append<T>(mock: AnyObject, call: MethodCall<T>, function: String) {
calls.append(call)
functions.append(function)
isVerified.append(false)
}

public func verify<T>(
mock: AnyObject,
matcher match: ArgumentMatcher<T>,
times: (Int) -> Bool,
type: String,
function: String
) {
var callCount = 0
var indexes: [Array.Index] = []
for index in calls.startIndex..<calls.endIndex {
guard !isVerified[index] else {
continue
}
guard functions[index] == function else {
continue
}
guard let call = calls[index] as? MethodCall<T> else {
continue
}
guard match(call.arguments) else {
continue
}
indexes.append(index)
callCount += 1
}
guard times(callCount) else {
testFailureReport("\(type).\(function): incorrect calls count: \(callCount)")
return
}
for index in indexes {
isVerified[index] = true
}
}
}
2 changes: 1 addition & 1 deletion Sources/SwiftMock/When/AsyncMethodInvocation.swift
Original file line number Diff line number Diff line change
@@ -33,7 +33,7 @@ public final class AsyncMethodInvocation<Arguments, Result> {
in container: [AsyncMethodInvocation<Arguments, Result>],
with arguments: Arguments,
type: String,
function: String = #function
function: String
) async -> Result {
guard let invocation = container.last(where: { invocation in
invocation.match(arguments)
2 changes: 1 addition & 1 deletion Sources/SwiftMock/When/AsyncThrowsMethodInvocation.swift
Original file line number Diff line number Diff line change
@@ -33,7 +33,7 @@ public final class AsyncThrowsMethodInvocation<Arguments, Result> {
in container: [AsyncThrowsMethodInvocation<Arguments, Result>],
with arguments: Arguments,
type: String,
function: String = #function
function: String
) async throws -> Result {
guard let invocation = container.last(where: { invocation in
invocation.match(arguments)
2 changes: 1 addition & 1 deletion Sources/SwiftMock/When/MethodInvocation.swift
Original file line number Diff line number Diff line change
@@ -33,7 +33,7 @@ public final class MethodInvocation<Arguments, Result> {
in container: [MethodInvocation<Arguments, Result>],
with arguments: Arguments,
type: String,
function: String = #function
function: String
) -> Result {
guard let invocation = container.last(where: { invocation in
invocation.match(arguments)
2 changes: 1 addition & 1 deletion Sources/SwiftMock/When/ThrowsMethodInvocation.swift
Original file line number Diff line number Diff line change
@@ -33,7 +33,7 @@ public final class ThrowsMethodInvocation<Arguments, Result> {
in container: [ThrowsMethodInvocation<Arguments, Result>],
with arguments: Arguments,
type: String,
function: String = #function
function: String
) throws -> Result {
guard let invocation = container.last(where: { invocation in
invocation.match(arguments)
26 changes: 18 additions & 8 deletions Sources/SwiftMockMacros/General.swift
Original file line number Diff line number Diff line change
@@ -115,15 +115,21 @@ extension MockMacro {
returnType: TypeSyntax?
) -> GenericArgumentClauseSyntax where T.Element == TypeSyntax {
GenericArgumentClauseSyntax {
GenericArgumentSyntax(
argument: makeTupleType(from: arguments)
)
packTypesToGenericArgumentSyntax(types: arguments)
GenericArgumentSyntax(
argument: returnType ?? voidType
)
}
}

static func packTypesToGenericArgumentSyntax<T: Collection>(
types: T
) -> GenericArgumentSyntax where T.Element == TypeSyntax {
GenericArgumentSyntax(
argument: makeTupleType(from: types)
)
}

private static func makeTupleType<T: Collection>(
from types: T
) -> TypeSyntax where T.Element == TypeSyntax {
@@ -203,11 +209,15 @@ extension MockMacro {

static func makeArgumentMatcherZipStmts(tokens: [TokenSyntax]) -> [DeclSyntax] {
var stmts: [DeclSyntax] = []
for (index, token) in tokens.enumerated().reversed() {
if index == tokens.count - 1 {
stmts.append("let argumentMatcher\(raw: index) = \(raw: token.text)")
} else {
stmts.append("let argumentMatcher\(raw: index) = zip(\(raw: token.text), argumentMatcher\(raw: index + 1))")
if tokens.isEmpty {
stmts.append("let argumentMatcher0: ArgumentMatcher<()> = any()")
} else {
for (index, token) in tokens.enumerated().reversed() {
if index == tokens.count - 1 {
stmts.append("let argumentMatcher\(raw: index) = \(raw: token.text)")
} else {
stmts.append("let argumentMatcher\(raw: index) = zip(\(raw: token.text), argumentMatcher\(raw: index + 1))")
}
}
}
return stmts
53 changes: 53 additions & 0 deletions Sources/SwiftMockMacros/Message.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
//
// Message.swift
//
//
// Created by Alexandr Zalutskiy on 15/10/2023.
//

import SwiftSyntax
import SwiftSyntaxBuilder

enum MessageError: Error {
case message(String)
}

extension MockMacro {
static func makeFunctionSignatureString(funcDecl: FunctionDeclSyntax) throws -> String {
var literal = ""
literal += funcDecl.name.text
literal += "("
for parameter in funcDecl.signature.parameterClause.parameters {
literal += parameter.firstName.text
literal += ":"
}
literal += ")"
if funcDecl.signature.effectSpecifiers?.asyncSpecifier != nil {
literal += " async"
}
if funcDecl.signature.effectSpecifiers?.throwsSpecifier != nil {
literal += " throws"
}
if let returnClause = funcDecl.signature.returnClause {
literal += " -> "
let type = String(returnClause.type.trimmed.syntaxTextBytes.map { Unicode.Scalar($0) }.map { Character($0) })
literal += type
}
return literal
}

static func makePropertySignatureString(
bindingSyntax: PatternBindingSyntax,
accessorDecl: AccessorDeclSyntax
) throws -> String {
guard let text = bindingSyntax.pattern.as(IdentifierPatternSyntax.self)?.identifier.text else {
throw MessageError.message("For property supported only decloration using IdentifierPatternSyntax")
}
var literal = ""
literal += text
if accessorDecl.accessorSpecifier.trimmed.text == TokenSyntax.keyword(.set).text {
literal += "="
}
return literal
}
}
51 changes: 38 additions & 13 deletions Sources/SwiftMockMacros/Property.swift
Original file line number Diff line number Diff line change
@@ -3,7 +3,7 @@ import SwiftSyntaxBuilder

extension MockMacro {

static func makeVariableMock(from variableDecl: VariableDeclSyntax, mockTypeToken: TokenSyntax) -> [DeclSyntax] {
static func makeVariableMock(from variableDecl: VariableDeclSyntax, mockTypeToken: TokenSyntax) throws -> [DeclSyntax] {
var declarations: [DeclSyntax] = []
for bindingSyntax in variableDecl.bindings {
guard let accessorBlock = bindingSyntax.accessorBlock else {
@@ -16,10 +16,9 @@ extension MockMacro {
}
for accessorDecl in accessorList {
declarations.append(makeInvocationContainerProperty(patternBinding: bindingSyntax, accessorDecl: accessorDecl))
declarations.append(makeCallStorageProperty(bindingSyntax: bindingSyntax, accessorDecl: accessorDecl))
declarations.append(makeSignatureMethod(patternBinding: bindingSyntax, accessorDecl: accessorDecl))
}
declarations.append(makeMockProperty(bindingSyntax: bindingSyntax, mockTypeToken: mockTypeToken))
declarations.append(try makeMockProperty(bindingSyntax: bindingSyntax, mockTypeToken: mockTypeToken))
}
return declarations
}
@@ -257,15 +256,19 @@ extension MockMacro {

// MARK: - Making the Mock Property

static func makeMockProperty(bindingSyntax: PatternBindingSyntax, mockTypeToken: TokenSyntax) -> DeclSyntax {
var accessorDeclListSyntax = AccessorDeclListSyntax {
AccessorDeclSyntax(
static func makeMockProperty(bindingSyntax: PatternBindingSyntax, mockTypeToken: TokenSyntax) throws -> DeclSyntax {
var accessorDeclListSyntax = try AccessorDeclListSyntax {
try AccessorDeclSyntax(
accessorSpecifier: .keyword(.get)
) {
"let arguments = ()"
makeStoreCallToStorageExpr(bindingSyntax: bindingSyntax, accessorDecl: AccessorDeclSyntax(accessorSpecifier: .keyword(.get)))
try makeStoreCallToStorageExpr(bindingSyntax: bindingSyntax, accessorDecl: AccessorDeclSyntax(accessorSpecifier: .keyword(.get)))
ReturnStmtSyntax(
expression: makeMockGetterReturnExpr(bindingSyntax: bindingSyntax, mockTypeToken: mockTypeToken)
expression: try makeMockGetterReturnExpr(
bindingSyntax: bindingSyntax,
accessorDecl: AccessorDeclSyntax(accessorSpecifier: .keyword(.get)),
mockTypeToken: mockTypeToken
)
)
}
}
@@ -280,13 +283,17 @@ extension MockMacro {
}
if hasSetter {
accessorDeclListSyntax.append(
AccessorDeclSyntax(
try AccessorDeclSyntax(
accessorSpecifier: .keyword(.set)
) {
"let arguments = (newValue)"
makeStoreCallToStorageExpr(bindingSyntax: bindingSyntax, accessorDecl: AccessorDeclSyntax(accessorSpecifier: .keyword(.set)))
try makeStoreCallToStorageExpr(bindingSyntax: bindingSyntax, accessorDecl: AccessorDeclSyntax(accessorSpecifier: .keyword(.set)))
ReturnStmtSyntax(
expression: makeMockSetterReturnExpr(bindingSyntax: bindingSyntax, mockTypeToken: mockTypeToken)
expression: try makeMockSetterReturnExpr(
bindingSyntax: bindingSyntax,
accessorDecl: AccessorDeclSyntax(accessorSpecifier: .keyword(.set)),
mockTypeToken: mockTypeToken
)
)
}
)
@@ -312,8 +319,13 @@ extension MockMacro {
)
}

private static func makeMockGetterReturnExpr(bindingSyntax: PatternBindingSyntax, mockTypeToken: TokenSyntax) -> ExprSyntax {
private static func makeMockGetterReturnExpr(
bindingSyntax: PatternBindingSyntax,
accessorDecl: AccessorDeclSyntax,
mockTypeToken: TokenSyntax
) throws -> ExprSyntax {
let invocationType = TokenSyntax.identifier("MethodInvocation")
let propertySignatureString = try makePropertySignatureString(bindingSyntax: bindingSyntax, accessorDecl: accessorDecl)
return ExprSyntax(
fromProtocol: FunctionCallExprSyntax(
calledExpression: MemberAccessExprSyntax(
@@ -335,12 +347,21 @@ extension MockMacro {
label: "type",
expression: StringLiteralExprSyntax(content: mockTypeToken.text)
)
LabeledExprSyntax(
label: "function",
expression: ExprSyntax(literal: propertySignatureString)
)
}
)
}

private static func makeMockSetterReturnExpr(bindingSyntax: PatternBindingSyntax, mockTypeToken: TokenSyntax) -> ExprSyntax {
private static func makeMockSetterReturnExpr(
bindingSyntax: PatternBindingSyntax,
accessorDecl: AccessorDeclSyntax,
mockTypeToken: TokenSyntax
) throws -> ExprSyntax {
let invocationType = TokenSyntax.identifier("MethodInvocation")
let propertySignatureString = try makePropertySignatureString(bindingSyntax: bindingSyntax, accessorDecl: accessorDecl)
return ExprSyntax(
fromProtocol: FunctionCallExprSyntax(
calledExpression: MemberAccessExprSyntax(
@@ -362,6 +383,10 @@ extension MockMacro {
label: "type",
expression: StringLiteralExprSyntax(content: mockTypeToken.text)
)
LabeledExprSyntax(
label: "function",
expression: ExprSyntax(literal: propertySignatureString)
)
}
)
}
42 changes: 17 additions & 25 deletions Sources/SwiftMockMacros/SwiftMockMacro.swift
Original file line number Diff line number Diff line change
@@ -20,7 +20,7 @@ public struct MockMacro: PeerMacro {

return [
DeclSyntax(
ClassDeclSyntax(
try ClassDeclSyntax(
modifiers: DeclModifierListSyntax {
DeclModifierSyntax(name: .keyword(.public))
DeclModifierSyntax(name: .keyword(.final))
@@ -31,19 +31,19 @@ public struct MockMacro: PeerMacro {
InheritedTypeSyntax(type: IdentifierTypeSyntax(name: "Verifiable"))
}
) {
makeVerifyType(declaration)
try makeVerifyType(declaration)
try makeVerifyCallStorageProperty()
for member in declaration.memberBlock.members {
if let funcDecl = member.decl.as(FunctionDeclSyntax.self) {
makeInvocationContainerProperty(funcDecl: funcDecl)
makeCallStorageProperty(funcDecl: funcDecl)
makeSignatureMethod(from: funcDecl)
funcDecl
.with(\.modifiers, DeclModifierListSyntax {
DeclModifierSyntax(name: .keyword(.public))
})
.with(\.body, makeMockMethodBody(from: funcDecl, type: mockTypeToken))
.with(\.body, try makeMockMethodBody(from: funcDecl, type: mockTypeToken))
} else if let variableDecl = member.decl.as(VariableDeclSyntax.self) {
for decl in makeVariableMock(from: variableDecl, mockTypeToken: mockTypeToken) {
for decl in try makeVariableMock(from: variableDecl, mockTypeToken: mockTypeToken) {
decl
}
}
@@ -98,23 +98,10 @@ public struct MockMacro: PeerMacro {
leftParen: .leftParenToken(),
rightParen: .rightParenToken()
) {
if parameters.isEmpty {
LabeledExprSyntax(
label: "argumentMatcher",
expression: FunctionCallExprSyntax(
calledExpression: DeclReferenceExprSyntax(
baseName: .identifier("any")
),
leftParen: .leftParenToken(),
rightParen: .rightParenToken()
) { }
)
} else {
LabeledExprSyntax(
label: "argumentMatcher",
expression: DeclReferenceExprSyntax(baseName: .identifier("argumentMatcher0"))
)
}
LabeledExprSyntax(
label: "argumentMatcher",
expression: ExprSyntax(stringLiteral: "argumentMatcher0")
)
LabeledExprSyntax(
label: "register",
expression: ClosureExprSyntax {
@@ -138,10 +125,11 @@ public struct MockMacro: PeerMacro {
})
}

private static func makeMockMethodBody(from funcDecl: FunctionDeclSyntax, type: TokenSyntax) -> CodeBlockSyntax {
private static func makeMockMethodBody(from funcDecl: FunctionDeclSyntax, type: TokenSyntax) throws -> CodeBlockSyntax {
let prefix = makeTypePrefix(funcDecl: funcDecl)
let invocationType = TokenSyntax.identifier(prefix + "MethodInvocation")
let argumentsExpression = packParametersToTupleExpr(funcDecl.signature.parameterClause.parameters)
let funcSignatureString = try makeFunctionSignatureString(funcDecl: funcDecl)
let functionCallExpr = FunctionCallExprSyntax(
calledExpression: MemberAccessExprSyntax(
base: DeclReferenceExprSyntax(baseName: invocationType),
@@ -162,10 +150,14 @@ public struct MockMacro: PeerMacro {
label: "type",
expression: StringLiteralExprSyntax(content: type.text)
)
LabeledExprSyntax(
label: "function",
expression: ExprSyntax(literal: funcSignatureString)
)
}
return CodeBlockSyntax {
return try CodeBlockSyntax {
"let arguments = \(argumentsExpression)"
makeStoreCallToStorageExpr(funcDecl: funcDecl)
try makeStoreCallToStorageExpr(funcDecl: funcDecl)
if funcDecl.signature.effectSpecifiers?.throwsSpecifier != nil {
if funcDecl.signature.effectSpecifiers?.asyncSpecifier != nil {
ReturnStmtSyntax(
124 changes: 54 additions & 70 deletions Sources/SwiftMockMacros/Verify.swift
Original file line number Diff line number Diff line change
@@ -2,8 +2,8 @@ import SwiftSyntax
import SwiftSyntaxBuilder

extension MockMacro {
static func makeVerifyType(_ protocolDecl: ProtocolDeclSyntax) -> StructDeclSyntax {
StructDeclSyntax(
static func makeVerifyType(_ protocolDecl: ProtocolDeclSyntax) throws -> StructDeclSyntax {
try StructDeclSyntax(
modifiers: DeclModifierListSyntax {
DeclModifierSyntax(name: .keyword(.public))
},
@@ -13,13 +13,14 @@ extension MockMacro {
}
) {
makeVerifyStorageProperty(protocolDecl: protocolDecl)
makeCallContainerProperty()
makeTimesStorageProperty()
makeInit(protocolDecl: protocolDecl)
for member in protocolDecl.memberBlock.members {
if let funcDecl = member.decl.as(FunctionDeclSyntax.self) {
makeVerifyMethod(protocolDecl: protocolDecl, funcDecl: funcDecl)
try makeVerifyMethod(protocolDecl: protocolDecl, funcDecl: funcDecl)
} else if let variableDecl = member.decl.as(VariableDeclSyntax.self) {
for decl in makeVerifyProperty(protocolDecl: protocolDecl, variableDecl: variableDecl) {
for decl in try makeVerifyProperty(protocolDecl: protocolDecl, variableDecl: variableDecl) {
decl
}
}
@@ -51,6 +52,22 @@ extension MockMacro {
)
}

private static func makeCallContainerProperty() -> VariableDeclSyntax {
do {
return try VariableDeclSyntax("let container: CallContainer")
} catch {
fatalError()
}

// VariableDeclSyntax(
// .let,
// name: "container",
// type: TypeAnnotationSyntax(
// type: IdentifierTypeSyntax(name: .identifier("CallContainer"))
// )
// )
}

private static func makeInit(protocolDecl: ProtocolDeclSyntax) -> InitializerDeclSyntax {
InitializerDeclSyntax(
modifiers: DeclModifierListSyntax {
@@ -59,20 +76,23 @@ extension MockMacro {
signature: FunctionSignatureSyntax(
parameterClause: FunctionParameterClauseSyntax {
"mock: \(raw: protocolDecl.name.text)Mock"
"container: CallContainer"
"times: @escaping TimesMatcher"
}
),
bodyBuilder: {
"self.mock = mock"
"self.container = container"
"self.times = times"
}
)
}

// MARK: - Vertify Method for Method

private static func makeVerifyMethod(protocolDecl: ProtocolDeclSyntax, funcDecl: FunctionDeclSyntax) -> FunctionDeclSyntax {
funcDecl
private static func makeVerifyMethod(protocolDecl: ProtocolDeclSyntax, funcDecl: FunctionDeclSyntax) throws -> FunctionDeclSyntax {
let funcSignatureString = try makeFunctionSignatureString(funcDecl: funcDecl)
return funcDecl
.with(\.modifiers, DeclModifierListSyntax {
publicModifier
})
@@ -82,8 +102,8 @@ extension MockMacro {
))
.with(\.body, makeVerifyBody(
arguments: funcDecl.signature.parameterClause.parameters.map { $0.secondName ?? $0.firstName },
storagePropertyToken: makeCallStorageProperyName(funcDecl: funcDecl),
mockTypeToken: makeMockTypeToken(protocolDecl)
mockTypeToken: makeMockTypeToken(protocolDecl),
funcSignatureExpr: ExprSyntax(literal: funcSignatureString)
))
}

@@ -93,7 +113,7 @@ extension MockMacro {

// MARK: - Verify Method For Property

private static func makeVerifyProperty(protocolDecl: ProtocolDeclSyntax, variableDecl: VariableDeclSyntax) -> [DeclSyntax] {
private static func makeVerifyProperty(protocolDecl: ProtocolDeclSyntax, variableDecl: VariableDeclSyntax) throws -> [DeclSyntax] {
var declarations: [DeclSyntax] = []
for bindingSyntax in variableDecl.bindings {
guard let accessorBlock = bindingSyntax.accessorBlock else {
@@ -105,6 +125,7 @@ extension MockMacro {
continue
}
for accessorDecl in accessorList {
let funcSignatureString = try makePropertySignatureString(bindingSyntax: bindingSyntax, accessorDecl: accessorDecl)
let decl = DeclSyntax(
fromProtocol: FunctionDeclSyntax(
modifiers: DeclModifierListSyntax {
@@ -114,8 +135,8 @@ extension MockMacro {
signature: makeVerifyPropertyFunctionSignature(bindingSyntax: bindingSyntax, accessorDecl: accessorDecl),
body: makeVerifyBody(
arguments: makeArgumentList(accessorDecl: accessorDecl),
storagePropertyToken: makeCallStoragePropertyToken(bindingSyntax: bindingSyntax, accessorDecl: accessorDecl),
mockTypeToken: makeMockTypeToken(protocolDecl)
mockTypeToken: makeMockTypeToken(protocolDecl),
funcSignatureExpr: ExprSyntax(literal: funcSignatureString)
)
)
)
@@ -172,23 +193,6 @@ extension MockMacro {
return FunctionSignatureSyntax(parameterClause: functionParameterClause)
}


private static func makeCallStoragePropertyToken(
bindingSyntax: PatternBindingSyntax,
accessorDecl: AccessorDeclSyntax
) -> TokenSyntax {
let text: String
switch accessorDecl.accessorSpecifier.trimmed.text {
case TokenSyntax.keyword(.get).text:
text = makeGetterInvocationContainerToken(from: bindingSyntax).text
case TokenSyntax.keyword(.set).text:
text = makeSetterInvocationContainerToken(from: bindingSyntax).text
default:
fatalError("Unexpected accessor for property. Supported accessors: \"get\" and \"set\"")
}
return .identifier(text + "___call")
}

private static func makeArgumentList(accessorDecl: AccessorDeclSyntax) -> [TokenSyntax] {
switch accessorDecl.accessorSpecifier.trimmed.text {
case TokenSyntax.keyword(.get).text:
@@ -204,46 +208,48 @@ extension MockMacro {

private static func makeVerifyBody(
arguments: [TokenSyntax] = [],
storagePropertyToken: TokenSyntax,
mockTypeToken: TokenSyntax
mockTypeToken: TokenSyntax,
funcSignatureExpr: ExprSyntax
) -> CodeBlockSyntax {
CodeBlockSyntax {
return CodeBlockSyntax {
for stmt in makeArgumentMatcherZipStmts(tokens: arguments) {
stmt
}
FunctionCallExprSyntax(
calledExpression: MemberAccessExprSyntax(
base: DeclReferenceExprSyntax(baseName: .identifier("MethodCall")),
declName: DeclReferenceExprSyntax(baseName: .identifier("verify"))
),
calledExpression: ExprSyntax(stringLiteral: "container.verify"),
leftParen: .leftParenToken(),
rightParen: .rightParenToken()
) {
LabeledExprSyntax(
label: "in",
expression: MemberAccessExprSyntax(
base: DeclReferenceExprSyntax(baseName: .identifier("mock")),
declName: DeclReferenceExprSyntax(baseName: storagePropertyToken)
)
label: "mock",
expression: ExprSyntax(stringLiteral: "mock")
)
LabeledExprSyntax(
label: "matcher",
expression: DeclReferenceExprSyntax(baseName: arguments.isEmpty ? .identifier("any()") : .identifier("argumentMatcher0"))
expression: ExprSyntax(stringLiteral: "argumentMatcher0")
)
LabeledExprSyntax(
label: "times",
expression: DeclReferenceExprSyntax(baseName: .identifier("times"))
expression: ExprSyntax(stringLiteral: "times")
)
LabeledExprSyntax(
label: "type",
expression: StringLiteralExprSyntax(content: mockTypeToken.text)
)
LabeledExprSyntax(
label: "function",
expression: funcSignatureExpr
)
}
}
}

// MARK: - Integration to other files

static func makeVerifyCallStorageProperty() throws -> VariableDeclSyntax {
try VariableDeclSyntax("public let container = VerifyContainer()")
}

static func makeCallStorageProperty(funcDecl: FunctionDeclSyntax) -> VariableDeclSyntax {
let storageName = makeCallStorageProperyName(funcDecl: funcDecl)
let parameterTypes = funcDecl.signature.parameterClause.parameters.map { $0.type }
@@ -263,31 +269,9 @@ extension MockMacro {
)
}

static func makeStoreCallToStorageExpr(funcDecl: FunctionDeclSyntax) -> ExprSyntax {
"\(makeCallStorageProperyName(funcDecl: funcDecl)).append(MethodCall(arguments: arguments))"
}

static func makeCallStorageProperty(
bindingSyntax: PatternBindingSyntax,
accessorDecl: AccessorDeclSyntax
) -> DeclSyntax {
let storageName = makeCallStoragePropertyToken(bindingSyntax: bindingSyntax, accessorDecl: accessorDecl)

return DeclSyntax(
fromProtocol: VariableDeclSyntax(
modifiers: DeclModifierListSyntax {
DeclModifierSyntax(name: .keyword(.private))
},
bindingSpecifier: .keyword(.var),
bindings: PatternBindingListSyntax {
PatternBindingSyntax(
pattern: IdentifierPatternSyntax(identifier: storageName),
typeAnnotation: TypeAnnotationSyntax(type: makeStorageType(bindingSyntax: bindingSyntax, accessorDecl: accessorDecl)),
initializer: InitializerClauseSyntax(value: ArrayExprSyntax(expressions: []))
)
}
)
)
static func makeStoreCallToStorageExpr(funcDecl: FunctionDeclSyntax) throws -> ExprSyntax {
let functionSignature = try makeFunctionSignatureString(funcDecl: funcDecl)
return "container.append(mock: self, call: MethodCall(arguments: arguments), function: \"\(raw: functionSignature)\")"
}

private static func makeStorageType(
@@ -316,9 +300,9 @@ extension MockMacro {
return TypeSyntax(fromProtocol: ArrayTypeSyntax(element: elementType))
}

static func makeStoreCallToStorageExpr(bindingSyntax: PatternBindingSyntax, accessorDecl: AccessorDeclSyntax) -> ExprSyntax {
let storageToken = makeCallStoragePropertyToken(bindingSyntax: bindingSyntax, accessorDecl: accessorDecl)
return "\(storageToken).append(MethodCall(arguments: arguments))"
static func makeStoreCallToStorageExpr(bindingSyntax: PatternBindingSyntax, accessorDecl: AccessorDeclSyntax) throws -> ExprSyntax {
let propertySignatureString = try makePropertySignatureString(bindingSyntax: bindingSyntax, accessorDecl: accessorDecl)
return "container.append(mock: self, call: MethodCall(arguments: arguments), function: \"\(raw: propertySignatureString)\")"
}

private static func makeGenericType(_ name: TokenSyntax, from funcDecl: FunctionDeclSyntax) -> some TypeSyntaxProtocol {
130 changes: 78 additions & 52 deletions Tests/SwiftMockTests/Macro/MockMacroTests.swift

Large diffs are not rendered by default.

5 changes: 3 additions & 2 deletions Tests/SwiftMockTests/VerifyTests.swift
Original file line number Diff line number Diff line change
@@ -87,7 +87,7 @@ final class VerifyTests: XCTestCase {

_ = mock.call(argument0: 6, argument1: 0)

verify(mock, times: atLeast(firstTimeCount)).call()
verify(mock).call()
}

func testAtLeastOnce() {
@@ -102,12 +102,13 @@ final class VerifyTests: XCTestCase {
#endif

_ = mock.call(argument0: 9, argument1: 5)
_ = mock.call(argument0: 4, argument1: 5)

verify(mock, times: atLeastOnce()).call()

_ = mock.call(argument0: 4, argument1: 9)

verify(mock, times: atLeastOnce()).call()
verify(mock).call()
}

func testNever() {