Skip to content

Commit

Permalink
Add codegeneration to macro for verifying property getting and setting
Browse files Browse the repository at this point in the history
  • Loading branch information
MetalheadSanya committed Oct 10, 2023
1 parent 18e81e0 commit e8272cb
Show file tree
Hide file tree
Showing 83 changed files with 434 additions and 163 deletions.
55 changes: 54 additions & 1 deletion Sources/SwiftMock/Documentation.docc/Usage/Verifying.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,61 @@ 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.

### 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.

```swift
@Mock
public protocol SomeService {
var property: Int { get }
}


func test() {
let mock = SomeServiceMock()

when(mock.$propertyGetter())
.thenReturn(4)

_ = mock.property

verify(mock).propertyGetter()
}
```

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

### Checking that property was write

To verify that the mock property has been write, 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 write. The name of the method is `propertySetter(_:)`, where `property` is the name of your property. All rules regarding method call verification work similarly for properties. Additionally, you can pass any `AttributeMatcher` to the `propertySetter(_:)` method to check that a certain value has been set to the property.

```swift
@Mock
public protocol SomeService {
var property: Int { get set }
}


func test() {
let mock = SomeServiceMock()

when(mock.$propertySetter())
.thenReturn()

mock.property = 6

// Checking that any value has been set to the property.
verify(mock).propertySetter()

// Checking that 6 (six) has been set to the property.
verify(mock).propertySetter(eq(6))
}
```

### Checking that method was called with specific arguments

The methods of a `Verify` structure have all the same methods as your protocol with one exception. All method arguments are replaced with a wrapper in the form of ``ArgumentMatcher``. As with stubbing, the default argument value is ``any()``. If you want to check that a method was called with a certain argument, then you need to specify the appropriate ``ArgumentMatcher`` for the arguments you want to check.
The methods of a `Verify` structure have all the same methods as your protocol with one exception. All method arguments are replaced with a wrapper in the form of `ArgumentMatcher`. As with stubbing, the default argument value is ``any()``. If you want to check that a method was called with a certain argument, then you need to specify the appropriate `ArgumentMatcher` for the arguments you want to check.

```swift
@Mock
Expand All @@ -55,6 +107,7 @@ func test() {
verify(mock).getAlbumName(id: eq("id2"))
}
```

### Checking that method was called specific times

The ``verify(_:times:)`` method can check not only that the method was called once, but also a specific number of times. The `times` parameter of the ``verify(_:times:)`` method is used for this. This parameter type is ``TimesMatcher`` and you can use ``times(_:)``, ``never()``, ``atLeast(_:)``, ``atMost(_:)`` as examples, or create your own.
Expand Down
53 changes: 33 additions & 20 deletions Sources/SwiftMockMacros/General.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,19 @@ extension MockMacro {
)
}

static func makeMethodCallType(
arguments: [TypeSyntax]
) -> TypeSyntax {
TypeSyntax(
fromProtocol: IdentifierTypeSyntax(
name: .identifier("MethodCall"),
genericArgumentClause: GenericArgumentClauseSyntax {
GenericArgumentSyntax(argument: makeTupleType(from: arguments))
}
)
)
}

private static func makeMethodWrapperType(
baseName: TokenSyntax,
isAsync: Bool,
Expand Down Expand Up @@ -114,33 +127,33 @@ extension MockMacro {
private static func makeTupleType<T: Collection>(
from types: T
) -> TypeSyntax where T.Element == TypeSyntax {
func packParametersToTupleType<Z: Collection>(
_ types: Z
) -> TupleTypeElementListSyntax where Z.Element == TypeSyntax {
if types.count <= 1 {
return TupleTypeElementListSyntax {
for type in types {
TupleTypeElementSyntax(type: type)
}
}
} else {
let rest = types.dropFirst()
return TupleTypeElementListSyntax {
TupleTypeElementSyntax(type: types.first!)
TupleTypeElementSyntax(
type: TupleTypeSyntax(elements: packParametersToTupleType(rest))
)
}
}
}

return TypeSyntax(
fromProtocol: TupleTypeSyntax(
elements: packParametersToTupleType(types)
)
)
}

private static func packParametersToTupleType<Z: Collection>(
_ types: Z
) -> TupleTypeElementListSyntax where Z.Element == TypeSyntax {
if types.count <= 1 {
return TupleTypeElementListSyntax {
for type in types {
TupleTypeElementSyntax(type: type)
}
}
} else {
let rest = types.dropFirst()
return TupleTypeElementListSyntax {
TupleTypeElementSyntax(type: types.first!)
TupleTypeElementSyntax(
type: TupleTypeSyntax(elements: packParametersToTupleType(rest))
)
}
}
}

// MARK: - Making Labeled Expressions

static func makeMethodSignatureRegisterLabeledExpr(from containerToken: TokenSyntax) -> LabeledExprSyntax {
Expand Down
21 changes: 12 additions & 9 deletions Sources/SwiftMockMacros/Property.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,21 @@ extension MockMacro {

static func makeVariableMock(from variableDecl: VariableDeclSyntax, mockTypeToken: TokenSyntax) -> [DeclSyntax] {
var declarations: [DeclSyntax] = []
for binding in variableDecl.bindings {
guard let accessorBlock = binding.accessorBlock else {
for bindingSyntax in variableDecl.bindings {
guard let accessorBlock = bindingSyntax.accessorBlock else {
// TODO: assertion
continue
}
guard case let .`accessors`(accessorList) = accessorBlock.accessors else {
// TODO: assertion
continue
}
for accessor in accessorList {
declarations.append(makeInvocationContainerProperty(patternBinding: binding, accessorDecl: accessor))
declarations.append(makeSignatureMethod(patternBinding: binding, accessorDecl: accessor))
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: binding, mockTypeToken: mockTypeToken))
declarations.append(makeMockProperty(bindingSyntax: bindingSyntax, mockTypeToken: mockTypeToken))
}
return declarations
}
Expand Down Expand Up @@ -83,7 +84,7 @@ extension MockMacro {
)
}

private static func makeGetterInvocationContainerToken(from bindingSyntax: PatternBindingSyntax) -> TokenSyntax {
static func makeGetterInvocationContainerToken(from bindingSyntax: PatternBindingSyntax) -> TokenSyntax {
let propertyPattern = bindingSyntax.pattern.as(IdentifierPatternSyntax.self) ?? IdentifierPatternSyntax(identifier: .identifier("unknown"))
return .identifier(propertyPattern.identifier.text + "___getter")
}
Expand Down Expand Up @@ -118,7 +119,7 @@ extension MockMacro {
)
}

private static func makeSetterInvocationContainerToken(from bindingSyntax: PatternBindingSyntax) -> TokenSyntax {
static func makeSetterInvocationContainerToken(from bindingSyntax: PatternBindingSyntax) -> TokenSyntax {
let propertyPattern = bindingSyntax.pattern.as(IdentifierPatternSyntax.self) ?? IdentifierPatternSyntax(identifier: .identifier("unknown"))
return .identifier(propertyPattern.identifier.text + "___setter")
}
Expand Down Expand Up @@ -262,6 +263,7 @@ extension MockMacro {
accessorSpecifier: .keyword(.get)
) {
"let arguments = ()"
makeStoreCallToStorageExpr(bindingSyntax: bindingSyntax, accessorDecl: AccessorDeclSyntax(accessorSpecifier: .keyword(.get)))
ReturnStmtSyntax(
expression: makeMockGetterReturnExpr(bindingSyntax: bindingSyntax, mockTypeToken: mockTypeToken)
)
Expand All @@ -282,6 +284,7 @@ extension MockMacro {
accessorSpecifier: .keyword(.set)
) {
"let arguments = (newValue)"
makeStoreCallToStorageExpr(bindingSyntax: bindingSyntax, accessorDecl: AccessorDeclSyntax(accessorSpecifier: .keyword(.set)))
ReturnStmtSyntax(
expression: makeMockSetterReturnExpr(bindingSyntax: bindingSyntax, mockTypeToken: mockTypeToken)
)
Expand Down Expand Up @@ -365,7 +368,7 @@ extension MockMacro {

// MARK: - General functions

private static func getBindingType(from bindingSyntax: PatternBindingSyntax) -> TypeSyntax {
static func getBindingType(from bindingSyntax: PatternBindingSyntax) -> TypeSyntax {
// TODO: error of unknown type
bindingSyntax.typeAnnotation?.type.trimmed ?? voidType
}
Expand Down
Loading

0 comments on commit e8272cb

Please sign in to comment.