Skip to content

Commit

Permalink
feat: add mocker fallback
Browse files Browse the repository at this point in the history
  • Loading branch information
Kolos65 committed Mar 19, 2024
1 parent 5c8b5e2 commit 239a25d
Show file tree
Hide file tree
Showing 3 changed files with 56 additions and 34 deletions.
4 changes: 2 additions & 2 deletions .swiftlint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,8 @@ conditional_returns_on_newline:
control_statement:
severity: error
cyclomatic_complexity:
warning: 7
error: 8
warning: 8
error: 9
ignores_case_statements: true
empty_parameters:
severity: error
Expand Down
69 changes: 37 additions & 32 deletions Sources/Mockable/Mocker/Mocker.swift
Original file line number Diff line number Diff line change
Expand Up @@ -115,16 +115,48 @@ public class Mocker<T: Mockable> {
/// - Returns: The expected return value.
@discardableResult
public func mock<V>(_ member: Member, producerResolver: (Any) throws -> V) throws -> V {
return try tryMock(member: member, producerResolver: producerResolver)
}

/// Mocks a void member, performing associated actions.
///
/// - Parameters:
/// - member: The member to mock.
/// - producerResolver: A closure resolving the produced value.
/// - Throws: An error if the mock encounters an issue.
public func mock(
_ member: Member, producerResolver: (Any) throws -> Void
) throws {
return try tryMock(
member: member,
producerResolver: producerResolver,
fallback: .value(())
)
}
}

// MARK: - Helpers

extension Mocker {
private func tryMock<V>(
member: Member,
producerResolver: (Any) throws -> V,
fallback: MockerFallback<V> = .none
) throws -> V {
addInvocation(for: member)
performActions(for: member)

let matchCount = returns[member]?
.filter { member.match($0.member) }
.count ?? 0

guard var candidates = returns[member], matchCount != 0 else {
let message = notMockedMessage(member, value: V.self)
fatalError(message)
guard var candidates = returns[member], matchCount != 0 else {
if case .value(let value) = fallback {
return value
} else {
let message = notMockedMessage(member, value: V.self)
fatalError(message)
}
}

for index in candidates.indices {
Expand Down Expand Up @@ -161,37 +193,10 @@ public class Mocker<T: Mockable> {
let message = genericNotMockedMessage(member, value: V.self)
fatalError(message)
}

/// Mocks a void member, performing associated actions.
///
/// - Parameters:
/// - member: The member to mock.
/// - producerResolver: A closure resolving the produced value.
/// - Throws: An error if the mock encounters an issue.
public func mock(_ member: Member, producerResolver: (Any) throws -> Void) throws {
addInvocation(for: member)
performActions(for: member)

guard var candidates = returns[member], !returns.isEmpty else { return }
let matchIndex = candidates.firstIndex {
member.match($0.member)
}
guard let matchIndex else { return }

let match = candidates[matchIndex]
candidates.remove(at: matchIndex)

returns[member] = candidates.isEmpty ? [match] : candidates

switch match.returnValue {
case .return: return
case .throw(let error): throw error
case .produce(let producer):
return try producerResolver(producer)
}
}
}

// MARK: - Error Messages

extension Mocker {
private func notMockedMessage<V>(_ member: Member, value: V.Type) -> String {
"""
Expand Down
17 changes: 17 additions & 0 deletions Sources/Mockable/Mocker/MockerFallback.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
//
// MockerFallback.swift
// Mockable
//
// Created by Kolos Foltanyi on 2024. 03. 17..
//

/// Describes an optional default value to use when no stored
/// return value found during mocking.
enum MockerFallback<V> {
/// Specifies a default value to be used when no stored return value is found.
case value(V)

/// Specifies that no default value should be used when no stored return value is found.
/// This results in a fatal error. This is the default behavior.
case none
}

0 comments on commit 239a25d

Please sign in to comment.