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

support swift-testing in MockManager.fail #518

Merged
merged 4 commits into from
Nov 3, 2024
Merged
Show file tree
Hide file tree
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
8 changes: 4 additions & 4 deletions Source/CuckooFunctions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ public func when<F: BaseStubFunctionTrait>(_ function: F) -> F {
}

/// Creates object used for verification of calls.
public func verify<M: Mock>(_ mock: M, _ callMatcher: CallMatcher = times(1), file: StaticString = #file, line: UInt = #line) -> M.Verification {
return mock.getVerificationProxy(callMatcher, sourceLocation: (file, line))
public func verify<M: Mock>(_ mock: M, _ callMatcher: CallMatcher = times(1), file: StaticString = #file, fileID: String = #fileID, filePath: String = #filePath, line: Int = #line, column: Int = #column) -> M.Verification {
return mock.getVerificationProxy(callMatcher, sourceLocation: (file, fileID, filePath, line, column))
}

/// Clears all invocations and stubs of mocks.
Expand All @@ -35,8 +35,8 @@ public func clearInvocations<M: Mock>(_ mocks: M...) {
}

/// Checks if there are no more uverified calls.
public func verifyNoMoreInteractions<M: Mock>(_ mocks: M..., file: StaticString = #file, line: UInt = #line) {
public func verifyNoMoreInteractions<M: Mock>(_ mocks: M..., file: StaticString = #file, fileID: String = #fileID, filePath: String = #filePath, line: Int = #line, column: Int = #column) {
mocks.forEach { mock in
mock.cuckoo_manager.verifyNoMoreInteractions((file, line))
mock.cuckoo_manager.verifyNoMoreInteractions((file, fileID, filePath, line, column))
}
}
23 changes: 19 additions & 4 deletions Source/MockManager.swift
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
#if canImport(Testing)
import Testing
#endif
#if canImport(XCTest)
import XCTest
#endif
Expand All @@ -6,8 +9,20 @@ public class MockManager {
public static var fail: ((message: String, sourceLocation: SourceLocation)) -> Void = { arg in
let (message, sourceLocation) = arg
#if canImport(XCTest)
XCTFail(message, file: sourceLocation.file, line: sourceLocation.line)
#else
XCTFail(message, file: sourceLocation.file, line: UInt(sourceLocation.line))
#endif
#if canImport(Testing)
Issue.record(
Comment(rawValue: message),
sourceLocation: Testing.SourceLocation(
fileID: sourceLocation.fileID,
filePath: sourceLocation.filePath,
line: sourceLocation.line,
column: sourceLocation.column
)
)
#endif
#if !canImport(XCTest) && !canImport(Testing)
print("\(Self.self).fail:", message, sourceLocation)
#endif
}
Expand Down Expand Up @@ -277,8 +292,8 @@ public class MockManager {
}
}

private func failAndCrash(_ message: String, file: StaticString = #file, line: UInt = #line) -> Never {
MockManager.fail((message: message, sourceLocation: (file, line)))
private func failAndCrash(_ message: String, file: StaticString = #file, fileID: String = #fileID, filePath: String = #filePath, line: Int = #line, column: Int = #column) -> Never {
MockManager.fail((message: message, sourceLocation: (file, fileID, filePath, line, column)))

#if _runtime(_ObjC)
NSException(name: .internalInconsistencyException, reason: message, userInfo: nil).raise()
Expand Down
2 changes: 1 addition & 1 deletion Source/Utils.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,4 @@ public func wrap<M: OptionalMatchable, IN, O>(matchable: M, mapping: @escaping (
}
}

public typealias SourceLocation = (file: StaticString, line: UInt)
public typealias SourceLocation = (file: StaticString, fileID: String, filePath: String, line: Int, column: Int)
148 changes: 148 additions & 0 deletions Tests/Swift/FailTest.swift
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import Testing
import XCTest
@testable import Cuckoo
@testable import CuckooMocks
Expand Down Expand Up @@ -123,3 +124,150 @@ final class FailTest: XCTestCase {
"6. withOptionalClosure(\"c\", Optional((Function)))"].joined(separator: "\n"))
}
}

struct FailSwiftTestingTests {
@Test
func missingInvocation() {
withKnownIssue {
let mock = MockTestedClass()

verify(mock).noReturn()
} matching: { issue in
issue.comments.map(\.rawValue) == [
"Wanted 1 times but not invoked."
]
}
}

@Test
func noInvocation2Wanted() {
withKnownIssue {
let mock = MockTestedClass()
stub(mock) { mock in
when(mock.noReturn()).thenDoNothing()
}

mock.noReturn()

verify(mock, times(2)).noReturn()
} matching: { issue in
issue.comments.map(\.rawValue) == [
"Wanted 2 times but invoked 1 times."
]
}
}

@Test
func invocationNeverWanted() {
withKnownIssue {
let mock = MockTestedClass()
stub(mock) { mock in
when(mock.noReturn()).thenDoNothing()
}

mock.noReturn()

verify(mock, never()).noReturn()
} matching: { issue in
issue.comments.map(\.rawValue) == [
"Wanted never but invoked 1 times."
]
}
}

@Test
func invocationAtLeast2Wanted() {
withKnownIssue {
let mock = MockTestedClass()
stub(mock) { mock in
when(mock.noReturn()).thenDoNothing()
}

mock.noReturn()

verify(mock, atLeast(2)).noReturn()
} matching: { issue in
issue.comments.map(\.rawValue) == [
"Wanted at least 2 times but invoked 1 times."
]
}
}

@Test
func invocationAtMost1Wanted() {
withKnownIssue {
let mock = MockTestedClass()
stub(mock) { mock in
when(mock.noReturn()).thenDoNothing()
}

mock.noReturn()
mock.noReturn()

verify(mock, atMost(1)).noReturn()
} matching: { issue in
issue.comments.map(\.rawValue) == [
"Wanted at most 1 times but invoked 2 times."
]
}
}

@Test
func callMatcherOr() {
withKnownIssue {
let mock = MockTestedClass()
stub(mock) { mock in
when(mock.noReturn()).thenDoNothing()
}

verify(mock, times(1).or(times(2))).noReturn()
} matching: { issue in
issue.comments.map(\.rawValue) == [
"Wanted either 1 times or 2 times but not invoked."
]
}
}

@Test
func callMatcherAnd() {
withKnownIssue {
let mock = MockTestedClass()
stub(mock) { mock in
when(mock.noReturn()).thenDoNothing()
}

verify(mock, atLeast(1).and(atMost(2))).noReturn()
} matching: { issue in
issue.comments.map(\.rawValue) == [
"Wanted both at least 1 times and at most 2 times but not invoked."
]
}
}

@Test
func verifyNoMoreInteractionsFail() {
withKnownIssue {
let mock = MockTestedClass().withEnabledSuperclassSpy()

mock.withOptionalClosure("a", closure: nil)
mock.noReturn()
_ = mock.count(characters: "b")
let _ = mock.readWriteProperty
mock.readWriteProperty = 1
mock.withOptionalClosure("c", closure: { _ in })

verifyNoMoreInteractions(mock)
} matching: { issue in
issue.comments.map(\.rawValue) == [
["No more interactions wanted but some found:",
"1. withOptionalClosure(\"a\", nil)",
"2. noReturn()",
"3. count(\"b\")",
"4. readWriteProperty#get",
"5. readWriteProperty#set(1)",
"6. withOptionalClosure(\"c\", Optional((Function)))"
].joined(separator: "\n")
]
}
}
}
97 changes: 96 additions & 1 deletion Tests/Swift/StubTest.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import XCTest
import Cuckoo
import Testing
import XCTest
@testable import CuckooMocks

final class StubTest: XCTestCase {
Expand Down Expand Up @@ -86,3 +87,97 @@ final class StubTest: XCTestCase {
stub.voidFunction()
}
}

struct StubSwiftTestingTests {
let stub = ClassForStubTestingStub()

@Test
func intProperty() {
stub.intProperty = 1
#expect(stub.intProperty == 0)
}

@Test
func arrayProperty() {
stub.arrayProperty = [1]
#expect(stub.arrayProperty == [])
}

@Test
func setProperty() {
stub.setProperty = Set([1])
#expect(stub.setProperty == Set())
}

@Test
func dictionaryProperty() {
stub.dictionaryProperty = ["a": 1]
#expect(stub.dictionaryProperty == [:])
}

@Test
func tuple1() {
stub.tuple1 = (2)
#expect(stub.tuple1 == (0))
}

@Test
func tuple2() {
stub.tuple2 = (2, 1)
#expect(stub.tuple2 == (0, 0))
}

@Test
func tuple3() {
stub.tuple3 = (2, 1, true)
#expect(stub.tuple3 == (0, 0, false))
}

@Test
func tuple4() {
stub.tuple4 = (2, 1, 1, 1)
#expect(stub.tuple4 == (0, 0, 0, 0))
}

@Test
func tuple5() {
stub.tuple5 = (2, 1, 1, 1, 1)
#expect(stub.tuple5 == (0, 0, 0, 0, 0))
}

@Test
func tuple6() {
stub.tuple6 = (2, "A", 1, 1, 1, 1)
#expect(stub.tuple6 == (0, "", 0, 0, 0, 0))
}

@Test
func intFunction() {
stub.intProperty = 1
#expect(stub.intFunction() == 0)
}

@Test
func arrayFunction() {
stub.arrayProperty = [1]
#expect(stub.arrayFunction() == [])
}

@Test
func setFunction() {
stub.setProperty = Set([1])
#expect(stub.setFunction() == Set())
}

@Test
func dictionaryFunction() {
stub.dictionaryProperty = ["a": 1]
#expect(stub.dictionaryFunction() == [:])
}

@Test
func testVoidFunction() {
// Test for crash
stub.voidFunction()
}
}