Skip to content

Commit

Permalink
feat: extend relaxed mode to custom types
Browse files Browse the repository at this point in the history
  • Loading branch information
Kolos65 committed Apr 5, 2024
1 parent 97ac3d9 commit 331bc67
Show file tree
Hide file tree
Showing 37 changed files with 317 additions and 354 deletions.
45 changes: 41 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -283,9 +283,9 @@ By default, you must specify a return value for all requirements; otherwise, a f
However, it is common to prefer avoiding this strict default behavior in favor of a more relaxed setting, where,
for example, void or optional return values do not need explicit `given` registration.

Use the **MockerPolicy** [option set](https://developer.apple.com/documentation/swift/optionset) to implicitly mock:
* only one kind of return value: `.relaxedOptional`
* construct a custom set of policies: `[.relaxedVoid, .relaxedOptional, .relaxedArray]`
Use [`MockerPolicy`](https://kolos65.github.io/Mockable/documentation/mockable/mockerpolicy) (which is an [option set](https://developer.apple.com/documentation/swift/optionset)) to implicitly mock:
* only one kind of return value: `.relaxedMockable`
* construct a custom set of policies: `[.relaxedVoid, .relaxedOptional]`
* or opt for a fully relaxed mode: `.relaxed`.

You have two options to override the default strict behavior of the library:
Expand All @@ -298,8 +298,45 @@ You have two options to override the default strict behavior of the library:
MockerPolicy.default = .relaxedVoid
```

> ⚠️ Relaxed mode will not work with generic functions as the type system is unable to locate the appropriate generic overload.
The `.relaxedMockable` policy in combination with the [`Mockable`](https://kolos65.github.io/Mockable/documentation/mockable/mockable) protocol can be used to set an implicit return value for custom types:
```swift
struct Car {
var name: String
var seats: Int
}
extension Car: Mockable {
static var mock: Car {
Car(name: "Mock Car", seats: 4)
}
// Defaults to [mock] but we can
// provide a custom array of cars:
static var mocks: [Car] {
[
Car(name: "Mock Car 1", seats: 4),
Car(name: "Mock Car 2", seats: 4)
]
}
}
@Mockable
protocol CarService {
func getCar() -> Car
func getCars() -> [Car]
}
func testCarService() {
func test() {
let mock = MockCarService(policy: .relaxedMockable)
// Implictly mocked without a given registration:
let car = mock.getCar()
let cars = mock.getCars()
}
}
```

> ⚠️ Relaxed mode will not work with generic return values as the type system is unable to locate the appropriate generic overload.

### Working with non-equatable Types
**Mockable** uses a `Matcher` internally to compare parameters.
Expand Down
17 changes: 0 additions & 17 deletions Sources/Mockable/Assertion/MockableAssertion.swift

This file was deleted.

2 changes: 1 addition & 1 deletion Sources/Mockable/Builder/EffectBuilder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
public protocol EffectBuilder<Service> {

/// The mock service associated with the Builder.
associatedtype Service: Mockable
associatedtype Service: MockService

/// Initializes a new instance of the builder with the provided `Mocker`.
///
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
///
/// This builder is used within the context of a higher-level builder (e.g., an `ActionBuilder`)
/// to specify a desired action to perform when a particular function of a mock service is called.
public struct FunctionActionBuilder<T: Mockable, ParentBuilder: EffectBuilder<T>> {
public struct FunctionActionBuilder<T: MockService, ParentBuilder: EffectBuilder<T>> {

/// Convenient type for the associated service's Member.
public typealias Member = T.Member
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
///
/// This builder is typically used within the context of a higher-level builder (e.g., a `ReturnBuilder`)
/// to specify the desired return value or a return value producer for a particular function of a mock service.
public struct FunctionReturnBuilder<T: Mockable, ParentBuilder: EffectBuilder<T>, ReturnType, ProduceType> {
public struct FunctionReturnBuilder<T: MockService, ParentBuilder: EffectBuilder<T>, ReturnType, ProduceType> {

/// Convenient type for the associated service's Member.
public typealias Member = T.Member
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
///
/// This builder is typically used within the context of a higher-level builder (e.g., a `VerifyBuilder`)
/// to verify the expected number of invocations for a particular function of a mock service.
public struct FunctionVerifyBuilder<T: Mockable, ParentBuilder: AssertionBuilder<T>> {
public struct FunctionVerifyBuilder<T: MockService, ParentBuilder: AssertionBuilder<T>> {

/// Convenient type for the associated service's Member.
public typealias Member = T.Member
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,5 @@
///
/// This builder is used within the context of a higher-level builder (e.g., an `ActionBuilder`)
/// to specify a desired action to perform when a particular throwing function of a mock service is called.
public typealias ThrowingFunctionActionBuilder<T: Mockable, ParentBuilder: EffectBuilder<T>>
public typealias ThrowingFunctionActionBuilder<T: MockService, ParentBuilder: EffectBuilder<T>>
= FunctionActionBuilder<T, ParentBuilder>
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
///
/// This builder is typically used within the context of a higher-level builder (e.g., a `ReturnBuilder`)
/// to specify the desired return value or a return value producer for a throwing function of a mock service.
public struct ThrowingFunctionReturnBuilder<T: Mockable, ParentBuilder: EffectBuilder<T>, ReturnType, ProduceType> {
public struct ThrowingFunctionReturnBuilder<T: MockService, ParentBuilder: EffectBuilder<T>, ReturnType, ProduceType> {

/// Convenient type for the associated service's Member.
public typealias Member = T.Member
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,5 @@
///
/// This builder is typically used within the context of a higher-level builder (e.g., a `VerifyBuilder`)
/// to verify the expected number of invocations for a throwing function of a mock service.
public typealias ThrowingFunctionVerifyBuilder<T: Mockable, ParentBuilder: AssertionBuilder<T>>
public typealias ThrowingFunctionVerifyBuilder<T: MockService, ParentBuilder: AssertionBuilder<T>>
= FunctionVerifyBuilder<T, ParentBuilder>
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
//
// Mockable.swift
// MockService.swift
// Mockable
//
// Created by Kolos Foltanyi on 2023. 11. 13..
//

/// A protocol defining the structure for a mockable service.
/// A protocol defining the structure for a mocked service.
///
/// Conforming types must provide a `Member` type representing their members
/// as well as builders for specifying return values, actions, and verifications.
public protocol Mockable {
public protocol MockService {

/// A type representing the members of the mocked protocol.
associatedtype Member: Matchable, CaseIdentifiable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
///
/// This builder is typically used within the context of a higher-level builder (e.g., an `ActionBuilder`)
/// to specify the behavior of the getter and setter of a particular property of a mock.
public struct PropertyActionBuilder<T: Mockable, ParentBuilder: EffectBuilder<T>> {
public struct PropertyActionBuilder<T: MockService, ParentBuilder: EffectBuilder<T>> {

/// Convenient type for the associated service's Member.
public typealias Member = T.Member
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
/// This builder is typically used within the context of a higher-level builder (e.g., a `ReturnBuilder`)
/// to specify the desired return value or a return value producer for the getter
/// of a particular property of a mock.
public struct PropertyReturnBuilder<T: Mockable, ParentBuilder: EffectBuilder<T>, ReturnType> {
public struct PropertyReturnBuilder<T: MockService, ParentBuilder: EffectBuilder<T>, ReturnType> {

/// Convenient type for the associated service's Member.
public typealias Member = T.Member
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
///
/// This builder is typically used within the context of a higher-level builder (e.g., a `VerifyBuilder`)
/// to verify the expected number of invocations for the getter and setter of a particular property of a mock.
public struct PropertyVerifyBuilder<T: Mockable, ParentBuilder: AssertionBuilder<T>> {
public struct PropertyVerifyBuilder<T: MockService, ParentBuilder: AssertionBuilder<T>> {

/// Convenient type for the associated service's Member.
public typealias Member = T.Member
Expand Down
13 changes: 12 additions & 1 deletion Sources/Mockable/Builder/VerifyBuilder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,21 @@
public protocol AssertionBuilder<Service> {

/// The mock service associated with the Builder.
associatedtype Service: Mockable
associatedtype Service: MockService

/// Initializes a new instance of the builder with the provided `Mocker`.
///
/// - Parameter mocker: The associated service's `Mocker` to provide for subsequent builders.
init(mocker: Mocker<Service>, assertion: @escaping MockableAssertion)
}

/// Typealias for assertion block.
///
/// The XCTest implementation of assertions is only available
/// from the `MockableTest` target. Import `MockableTest` in your test target and use its:
/// * `given(_ service:)`
/// * `when(_ service:)`
/// * `verify(_ service:)`
///
/// clauses for testing.
public typealias MockableAssertion = (@autoclosure () -> Bool, @autoclosure () -> String, StaticString, UInt) -> Void
45 changes: 41 additions & 4 deletions Sources/Mockable/Documentation/Documentation.docc/Usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -183,9 +183,9 @@ By default, you must specify a return value for all requirements; otherwise, a f
However, it is common to prefer avoiding this strict default behavior in favor of a more relaxed setting, where,
for example, void or optional return values do not need explicit `given` registration.

Use the **MockerPolicy** [option set](https://developer.apple.com/documentation/swift/optionset) to implicitly mock:
* only one kind of return value: `.relaxedOptional`
* construct a custom set of policies: `[.relaxedVoid, .relaxedOptional, .relaxedArray]`
Use the Use [`MockerPolicy`](https://kolos65.github.io/Mockable/documentation/mockable/mockerpolicy) (which is an [option set](https://developer.apple.com/documentation/swift/optionset)) to implicitly mock:
* only one kind of return value: `.relaxedMockable`
* construct a custom set of policies: `[.relaxedVoid, .relaxedOptional]`
* or opt for a fully relaxed mode: `.relaxed`.

You have two options to override the default strict behavior of the library:
Expand All @@ -198,8 +198,45 @@ You have two options to override the default strict behavior of the library:
MockerPolicy.default = .relaxedVoid
```

> ⚠️ Relaxed mode will not work with generic functions as the type system is unable to locate the appropriate generic overload.
The `.relaxedMockable` policy in combination with the [`Mockable`](https://kolos65.github.io/Mockable/documentation/mockable/mockable) protocol can be used to set an implicit return value for custom types (or even built in types):
```swift
struct Car {
var name: String
var seats: Int
}

extension Car: Mockable {
static var mock: Car {
Car(name: "Mock Car", seats: 4)
}

// Defaults to [mock] but we can
// provide a custom array of cars:
static var mocks: [Car] {
[
Car(name: "Mock Car 1", seats: 4),
Car(name: "Mock Car 2", seats: 4)
]
}
}

@Mockable
protocol CarService {
func getCar() -> Car
func getCars() -> [Car]
}

func testCarService() {
func test() {
let mock = MockCarService(policy: .relaxedMockable)
// Implictly mocked without a given registration:
let car = mock.getCar()
let cars = mock.getCars()
}
}
```

> ⚠️ Relaxed mode will not work with generic return values as the type system is unable to locate the appropriate generic overload.

## Working with non-equatable Types
**Mockable** uses a `Matcher` internally to compare parameters.
Expand Down
66 changes: 66 additions & 0 deletions Sources/Mockable/Mockable/Mockable.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
//
// Mockable.swift
// Mockable
//
// Created by Kolos Foltanyi on 2024. 04. 03..
//

/// A protocol that represents auto-mocked types.
///
/// `Mockable` in combination with a `relaxedMockable` option of `MockerPolicy `can be used
/// to set an implicit return value for custom types:
///
/// ```swift
/// struct Car {
/// var name: String
/// var seats: Int
/// }
///
/// extension Car: Mockable {
/// static var mock: Car {
/// Car(name: "Mock Car", seats: 4)
/// }
///
/// // Defaults to [mock] but we can
/// // provide a custom array of cars:
/// static var mocks: [Car] {
/// [
/// Car(name: "Mock Car 1", seats: 4),
/// Car(name: "Mock Car 2", seats: 4)
/// ]
/// }
/// }
///
/// @Mockable
/// protocol CarService {
/// func getCar() -> Car
/// func getCars() -> [Car]
/// }
///
/// func testCarService() {
/// func test() {
/// let mock = MockCarService(policy: .relaxedMockable)
/// // Implictly mocked without a given registration:
/// let car = mock.getCar()
/// let cars = mock.getCars()
/// }
/// }
/// ```
public protocol Mockable {
/// A default mock return value to use when `.relaxedMocked` policy is set.
static var mock: Self { get }

/// An array of mock values to use as return values when `.relaxedMocked` policy is set.
/// Defaults to `[Self.mock]`.
static var mocks: [Self] { get }
}

extension Mockable {
public static var mocks: [Self] { [mock] }
}

extension Array: Mockable where Element: Mockable {
public static var mock: Self {
Element.mocks
}
}
Loading

0 comments on commit 331bc67

Please sign in to comment.