Skip to content
Open
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
1 change: 1 addition & 0 deletions Factory.xctestplan
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
},
"testTargets" : [
{
"parallelizable" : true,
"target" : {
"containerPath" : "container:",
"identifier" : "FactoryTests",
Expand Down
15 changes: 7 additions & 8 deletions Sources/FactoryTesting/ContainerTrait.swift
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ import Testing
///
/// If you use a custom container, you have to create your own trait and container variable extensions.
///
/// That said, it's also possible to leverage this behavior in `XCTestCase` by using the `@TaskLocal` provided `withValue` method.
/// That said, it's also possible to leverage this behavior in XCTesting by inheriting `XCContainerTestCase` instead of `XCTestCase`.
///
/// See the `Testing` documentation and examples in the `ParallelTests` and `ParallelXCTests` files.
public struct ContainerTrait<C: SharedContainer>: TestTrait, SuiteTrait, TestScoping {
Expand All @@ -62,20 +62,19 @@ public struct ContainerTrait<C: SharedContainer>: TestTrait, SuiteTrait, TestSco
}

public func provideScope(for test: Test, testCase: Test.Case?, performing function: () async throws -> Void) async throws {
try await Scope.$singleton.withValue(Scope.singleton.clone()) {
try await shared.withValue(container()) {
await transform?(C.shared)
try await function()
}
}
try await withContainer(
shared: self.shared,
container: self.container(),
operation: function,
transform: transform
)
}

public func callAsFunction(transform: @escaping Transform) -> Self {
var copy = self
copy.transform = transform
return copy
}

}

/// Provides test trait for default container
Expand Down
33 changes: 33 additions & 0 deletions Sources/FactoryTesting/WithContainer.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import FactoryKit

/// Asynchronously provides a new container and singleton scope for each operation.
public func withContainer<C: SharedContainer>(
shared: TaskLocal<C>,
container: @autoclosure @Sendable () -> C,
operation: () async throws -> Void,
transform: ContainerTrait<C>.Transform? = nil
) async rethrows {
try await Scope.$singleton.withValue(Scope.singleton.clone()) {
try await shared.withValue(container()) {
await transform?(C.shared)
try await operation()
}
}
}

public typealias SynchronousTransform<C: SharedContainer> = @Sendable (C) -> Void

/// Synchronous version of `withContainer` for use in non-async contexts.
public func withContainer<C: SharedContainer>(
shared: TaskLocal<C>,
container: @autoclosure @Sendable () -> C,
operation: () throws -> Void,
transform: SynchronousTransform<C>? = nil
) rethrows {
try Scope.$singleton.withValue(Scope.singleton.clone()) {
try shared.withValue(container()) {
transform?(C.shared)
try operation()
}
}
}
20 changes: 20 additions & 0 deletions Sources/FactoryTesting/XCContainerTestCase.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import FactoryKit
import XCTest

/// Provides a unique `Container` and a singleton scope to every unit test function in the class. Mimicking the behavior of `ContainerTrait` for `swift-testing`.
open class XCContainerTestCase: XCTestCase {

/// The optional transformation to apply to the `Container` before invoking the test.
/// Due to the nature of XCTest, this is not async and should be used for synchronous transformations only.
open var transform: (@Sendable (Container) -> Void)?

/// Scopes the unit test function to a unique singleton scope and a `Container` instance transformed via the `transform` variable (if overridden to non-nil).
public override func invokeTest() {
withContainer(
shared: Container.$shared,
container: Container(),
operation: super.invokeTest,
transform: self.transform
)
}
}
8 changes: 2 additions & 6 deletions Tests/FactoryTests/FactoryComponentTests.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import XCTest
import FactoryTesting
@testable import FactoryKit

let key1String = StaticString(stringLiteral: "s1")
Expand All @@ -7,7 +8,7 @@ let key2String = StaticString(stringLiteral: "s2")
let key3Unicode = MyStaticScalar("\u{1F600}").value
let key4Unicode = MyStaticScalar("\u{1F601}").value

final class FactoryComponentTests: XCTestCase {
final class FactoryComponentTests: XCContainerTestCase {

let key1 = FactoryKey(type: UUID.self, key: key1String)
let key1D = FactoryKey(type: UUID.self, key: key1StringDup)
Expand All @@ -16,11 +17,6 @@ final class FactoryComponentTests: XCTestCase {
let key3U = FactoryKey(type: UUID.self, key: key3Unicode)
let key4U = FactoryKey(type: UUID.self, key: key4Unicode)

override func setUp() {
super.setUp()
Container.shared.reset()
}

func testScopeCache() {
let cache = Scope.Cache()
let scopeID = UUID()
Expand Down
23 changes: 10 additions & 13 deletions Tests/FactoryTests/FactoryContainerTests.swift
Original file line number Diff line number Diff line change
@@ -1,23 +1,12 @@
import XCTest
import FactoryTesting
@testable import FactoryKit

#if canImport(SwiftUI)
import SwiftUI
#endif

final class FactoryContainerTests: XCTestCase {

override func setUp() {
super.setUp()
Container.shared.reset()
CustomContainer.shared.reset()
}

func testDecorators() {
CustomContainer.count = 0
let _ = CustomContainer.shared.decorated()
XCTAssertEqual(CustomContainer.shared.count, 2)
}
final class FactoryContainerTests: XCContainerTestCase {

func testPushPop() throws {
let service1 = Container.shared.myServiceType()
Expand Down Expand Up @@ -112,6 +101,14 @@ final class FactoryContainerTests: XCTestCase {

}

final class FactoryCustomContainerTests: XCCustomContainerTestCase {
func testDecorators() {
CustomContainer.count = 0
let _ = CustomContainer.shared.decorated()
XCTAssertEqual(CustomContainer.shared.count, 2)
}
}

private extension Container {
var cachedCoverage: Factory<MyService?> { self { MyService() }.cached }
var graphCoverage: Factory<MyService?> { self { MyService() }.graph }
Expand Down
6 changes: 2 additions & 4 deletions Tests/FactoryTests/FactoryContextTests.swift
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
import XCTest
import FactoryTesting
@testable import FactoryKit

final class FactoryContextTests: XCTestCase {
final class FactoryContextTests: XCContainerTestCase {

override func setUp() {
super.setUp()

// start over
Container.shared.reset()

// externally defined contexts
Container.shared.externalContextService
.register { ContextService(name: "REGISTERED") }
Expand Down
66 changes: 35 additions & 31 deletions Tests/FactoryTests/FactoryCoreTests.swift
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
import XCTest
import FactoryTesting
@testable import FactoryKit

#if canImport(SwiftUI)
import SwiftUI
#endif

final class FactoryCoreTests: XCTestCase {
final class FactoryCoreTests: XCContainerAndCustomContainerTestCase {

override func setUp() {
Container.shared.reset()
CustomContainer.shared.reset()
CustomContainer.shared.count = 0
}

Expand Down Expand Up @@ -193,34 +192,6 @@ final class FactoryCoreTests: XCTestCase {
}
}

@MainActor
func testStrictPromise() {
// Expect non fatal error when strict and NOT in debug mode
Container.shared.manager.promiseTriggersError = false
expectNonFatalError {
let _ = Container.shared.strictPromisedService()
}
// Expect fatal error when strict and in debug mode
Container.shared.manager.promiseTriggersError = true
expectFatalError(expectedMessage: "MyServiceType was not registered") {
let _ = Container.shared.strictPromisedService()
}
}

@MainActor
func testStrictParameterPromise() {
// Expect non fatal error when strict and NOT in debug mode
Container.shared.manager.promiseTriggersError = false
expectNonFatalError {
let _ = Container.shared.strictPromisedParameterService(23)
}
// Expect fatal error when strict and in debug mode
Container.shared.manager.promiseTriggersError = true
expectFatalError(expectedMessage: "ParameterService was not registered") {
let _ = Container.shared.strictPromisedParameterService(23)
}
}

func testTrace() {
var logged: [String] = []
Container.shared.manager.trace.toggle()
Expand Down Expand Up @@ -257,3 +228,36 @@ final class FactoryCoreTests: XCTestCase {
#endif

}

// FactoryContext.current is not yet using @TaskLocal therefore we cannot use safely the `XCContainerTestCase` here.
final class FactoryCoreStrictPromiseTests: XCTestCase {

@MainActor
func testStrictPromise() {
// Expect non fatal error when strict and NOT in debug mode
Container.shared.manager.promiseTriggersError = false
expectNonFatalError {
let _ = Container.shared.strictPromisedService()
}
// Expect fatal error when strict and in debug mode
Container.shared.manager.promiseTriggersError = true
expectFatalError(expectedMessage: "MyServiceType was not registered") {
let _ = Container.shared.strictPromisedService()
}
}

@MainActor
func testStrictParameterPromise() {
// Expect non fatal error when strict and NOT in debug mode
Container.shared.manager.promiseTriggersError = false
expectNonFatalError {
let _ = Container.shared.strictPromisedParameterService(23)
}
// Expect fatal error when strict and in debug mode
Container.shared.manager.promiseTriggersError = true
expectFatalError(expectedMessage: "ParameterService was not registered") {
let _ = Container.shared.strictPromisedParameterService(23)
}
}

}
Loading
Loading