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

Clarify Default Override Behavior API #107

Merged
merged 1 commit into from
Jan 16, 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
12 changes: 6 additions & 6 deletions Sources/Knit/Module/DependencyBuilder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,17 @@ final class DependencyBuilder {
private var inputModules: [any ModuleAssembly] = []
var assemblies: [any ModuleAssembly] = []
let isRegisteredInParent: (any ModuleAssembly.Type) -> Bool
private let defaultOverrides: DefaultOverrideState
private let overrideBehavior: OverrideBehavior
private var moduleSources: [String: any ModuleAssembly.Type] = [:]

init(
modules: [any ModuleAssembly],
assemblyValidation: ((any ModuleAssembly.Type) throws -> Void)? = nil,
defaultOverrides: DefaultOverrideState = .whenTesting,
overrideBehavior: OverrideBehavior = .defaultOverridesWhenTesting,
isRegisteredInParent: ((any ModuleAssembly.Type) -> Bool)? = nil
) throws {
self.assemblyValidation = assemblyValidation
self.defaultOverrides = defaultOverrides
self.overrideBehavior = overrideBehavior

inputModules = modules
self.isRegisteredInParent = isRegisteredInParent ?? {_ in false }
Expand Down Expand Up @@ -134,7 +134,7 @@ final class DependencyBuilder {
_ moduleType: any ModuleAssembly.Type,
fromInput: Bool
) throws -> (any ModuleAssembly.Type)? {
guard defaultOverrides.allow,
guard overrideBehavior.allowDefaultOverrides,
!fromInput,
let defaultType = (moduleType as? any DefaultModuleAssemblyOverride.Type)
else {
Expand Down Expand Up @@ -199,7 +199,7 @@ extension DependencyBuilder {
switch self {
case let .moduleNotProvided(moduleType, sourcePath):
var testAdvice = ""
if DefaultOverrideState.isRunningTests {
if OverrideBehavior.isRunningTests {
testAdvice += "Adding a dependency on the testing module for \(moduleType) should fix this issue"
}
return """
Expand Down Expand Up @@ -240,7 +240,7 @@ extension ModuleAssembly {

/// All original dependencies are registered along with any additional dependencies that the override requires
static var combinedDependencies: [any ModuleAssembly.Type] {
if DefaultOverrideState.isRunningTests {
if OverrideBehavior.isRunningTests {
// For tests only take this modules dependencies
return Self.dependencies
} else {
Expand Down
14 changes: 8 additions & 6 deletions Sources/Knit/Module/ModuleAssembler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,16 @@ public final class ModuleAssembler {
- Parameters:
- parent: A ModuleAssembler that has already been setup with some dependencies.
- modules: Array of modules to register
- defaultOverrides: Array of override types to use when resolving modules
- overrideBehavior: Behavior of default override usage.
- assemblyValidation: An optional closure to perform custom validation on module assemblies for this assembler.
The Assembler will invoke this closure with each ModuleAssembly.Type as it performs its initialization.
If the closure throws an error for any of the assemblies then a fatal error will occur.
- postAssemble: Hook after all assemblies are registered to make changes to the container.

*/
public convenience init(
parent: ModuleAssembler? = nil,
_ modules: [any Assembly],
defaultOverrides: DefaultOverrideState = .whenTesting,
overrideBehavior: OverrideBehavior = .defaultOverridesWhenTesting,
assemblyValidation: ((any ModuleAssembly.Type) throws -> Void)? = nil,
postAssemble: ((Container) -> Void)? = nil,
file: StaticString = #file,
Expand All @@ -44,7 +46,7 @@ public final class ModuleAssembler {
try self.init(
parent: parent,
_modules: modules,
defaultOverrides: defaultOverrides,
overrideBehavior: overrideBehavior,
assemblyValidation: assemblyValidation,
postAssemble: postAssemble
)
Expand All @@ -63,7 +65,7 @@ public final class ModuleAssembler {
required init(
parent: ModuleAssembler? = nil,
_modules modules: [any Assembly],
defaultOverrides: DefaultOverrideState = .whenTesting,
overrideBehavior: OverrideBehavior = .defaultOverridesWhenTesting,
assemblyValidation: ((any ModuleAssembly.Type) throws -> Void)? = nil,
postAssemble: ((Container) -> Void)? = nil
) throws {
Expand All @@ -73,7 +75,7 @@ public final class ModuleAssembler {
self.builder = try DependencyBuilder(
modules: moduleAssemblies,
assemblyValidation: assemblyValidation,
defaultOverrides: defaultOverrides,
overrideBehavior: overrideBehavior,
isRegisteredInParent: { type in
return parent?.isRegistered(type) ?? false
}
Expand Down
24 changes: 18 additions & 6 deletions Sources/Knit/Module/ModuleAssembly.swift
Original file line number Diff line number Diff line change
Expand Up @@ -62,14 +62,26 @@ extension ModuleAssembly where Self: GeneratedModuleAssembly {
public static var dependencies: [any ModuleAssembly.Type] { scoped(generatedDependencies) }
}

public enum DefaultOverrideState {
case on, off, whenTesting
/// Control the behavior of Assembly Overrides.
public enum OverrideBehavior {

var allow: Bool {
/// Use any default overrides that are available.
case useDefaultOverrides

/// Disable and ignore any default overrides.
/// Overrides that are _explicitly provided_ will still be used.
case disableDefaultOverrides
Comment on lines +71 to +73
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@skorulis-ap can you confirm this is correct?


/// Use default overrides *based on the runtime context*.
/// If the ModuleAssembler is running in a unit test environment, the default overrides will be used.
/// Otherwise disable and ignore default overrides.
case defaultOverridesWhenTesting

var allowDefaultOverrides: Bool {
switch self {
case .on: return true
case .off: return false
case .whenTesting: return Self.isRunningTests
case .useDefaultOverrides: return true
case .disableDefaultOverrides: return false
case .defaultOverridesWhenTesting: return Self.isRunningTests
}
}

Expand Down
8 changes: 4 additions & 4 deletions Sources/Knit/Module/ScopedModuleAssembler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public final class ScopedModuleAssembler<ScopedResolver> {
public convenience init(
parent: ModuleAssembler? = nil,
_ modules: [any Assembly],
defaultOverrides: DefaultOverrideState = .whenTesting,
overrideBehavior: OverrideBehavior = .defaultOverridesWhenTesting,
postAssemble: ((Container) -> Void)? = nil,
file: StaticString = #file,
line: UInt = #line
Expand All @@ -26,7 +26,7 @@ public final class ScopedModuleAssembler<ScopedResolver> {
try self.init(
parent: parent,
_modules: modules,
defaultOverrides: defaultOverrides,
overrideBehavior: overrideBehavior,
postAssemble: postAssemble
)
} catch {
Expand All @@ -42,13 +42,13 @@ public final class ScopedModuleAssembler<ScopedResolver> {
required init(
parent: ModuleAssembler? = nil,
_modules modules: [any Assembly],
defaultOverrides: DefaultOverrideState = .whenTesting,
overrideBehavior: OverrideBehavior = .defaultOverridesWhenTesting,
postAssemble: ((Container) -> Void)? = nil
) throws {
self.internalAssembler = try ModuleAssembler(
parent: parent,
_modules: modules,
defaultOverrides: defaultOverrides,
overrideBehavior: overrideBehavior,
assemblyValidation: { moduleAssemblyType in
guard moduleAssemblyType.resolverType == ScopedResolver.self else {
throw ScopedModuleAssemblerError.incorrectTargetResolver(
Expand Down
28 changes: 17 additions & 11 deletions Tests/KnitTests/ModuleAssemblyOverrideTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,17 @@ import XCTest
final class ModuleAssemblyOverrideTests: XCTestCase {

func test_registrationWithoutFakes() throws {
let builder = try DependencyBuilder(modules: [Assembly2()], defaultOverrides: .off)
let builder = try DependencyBuilder(modules: [Assembly2()], overrideBehavior: .disableDefaultOverrides)
XCTAssertEqual(builder.assemblies.count, 2)
XCTAssertTrue(builder.assemblies[0] is Assembly1)
XCTAssertTrue(builder.assemblies[1] is Assembly2)
}

func test_registrationWithFakes() throws {
let builder = try DependencyBuilder(modules: [Assembly2(), Assembly2Fake()], defaultOverrides: .off)
let builder = try DependencyBuilder(
modules: [Assembly2(), Assembly2Fake()],
overrideBehavior: .disableDefaultOverrides
)
XCTAssertEqual(builder.assemblies.count, 3)
XCTAssertTrue(builder.assemblies[0] is Assembly1)
XCTAssertTrue(builder.assemblies[1] is FakeAssembly3)
Expand All @@ -33,28 +36,28 @@ final class ModuleAssemblyOverrideTests: XCTestCase {
}

func test_assemblerWithDefaultOverrides() {
let assembler = ModuleAssembler([Assembly2()], defaultOverrides: .on)
let assembler = ModuleAssembler([Assembly2()], overrideBehavior: .useDefaultOverrides)
XCTAssertTrue(assembler.registeredModules.contains(where: {$0 == Assembly1Fake.self}))
XCTAssertTrue(assembler.isRegistered(Assembly1Fake.self))
// Treat Assembly1 as being registered because the mock is
XCTAssertTrue(assembler.isRegistered(Assembly1.self))
}

func test_noDefaultOverrideForInputModules() {
let assembler = ModuleAssembler([Assembly1()], defaultOverrides: .on)
let assembler = ModuleAssembler([Assembly1()], overrideBehavior: .useDefaultOverrides)
XCTAssertTrue(assembler.isRegistered(Assembly1.self))
// The fake is not automatically registered
XCTAssertFalse(assembler.isRegistered(Assembly1Fake.self))
}

func test_explicitInputOverride() {
let assembler = ModuleAssembler([Assembly1(), Assembly1Fake()], defaultOverrides: .on)
let assembler = ModuleAssembler([Assembly1(), Assembly1Fake()], overrideBehavior: .useDefaultOverrides)
XCTAssertTrue(assembler.isRegistered(Assembly1.self))
XCTAssertTrue(assembler.isRegistered(Assembly1Fake.self))
}

func test_assemblerWithoutDefaultOverrides() {
let assembler = ModuleAssembler([Assembly2()], defaultOverrides: .off)
let assembler = ModuleAssembler([Assembly2()], overrideBehavior: .disableDefaultOverrides)
XCTAssertTrue(assembler.isRegistered(Assembly1.self))
XCTAssertFalse(assembler.isRegistered(Assembly1Fake.self))
}
Expand Down Expand Up @@ -83,29 +86,32 @@ final class ModuleAssemblyOverrideTests: XCTestCase {
func test_overrideDefaultOverride() {
let assembler = ModuleAssembler(
[Assembly4(), Assembly4Fake2()],
defaultOverrides: .on
overrideBehavior: .useDefaultOverrides
)
XCTAssertTrue(assembler.isRegistered(Assembly4.self))
XCTAssertTrue(assembler.isRegistered(Assembly4Fake2.self))
XCTAssertFalse(assembler.isRegistered(Assembly4Fake.self))
}

func test_nonAutoOverride() throws {
let builder = try DependencyBuilder(modules: [Assembly1(), NonAutoOverride()], defaultOverrides: .off)
let builder = try DependencyBuilder(
modules: [Assembly1(), NonAutoOverride()],
overrideBehavior: .disableDefaultOverrides
)
XCTAssertTrue(builder.assemblies.first is NonAutoOverride)
}

func test_parentNonAutoOverride() {
let parent = ModuleAssembler([NonAutoOverride()])
let child = ModuleAssembler(parent: parent, [Assembly1()], defaultOverrides: .off)
let child = ModuleAssembler(parent: parent, [Assembly1()], overrideBehavior: .disableDefaultOverrides)
XCTAssertTrue(child.isRegistered(Assembly1.self))
XCTAssertTrue(child.registeredModules.isEmpty)
}

func test_multipleOverrides() {
let assembler = ModuleAssembler(
[MultipleDependencyAssembly(), MultipleOverrideAssembly()],
defaultOverrides: .off
overrideBehavior: .disableDefaultOverrides
)

XCTAssertTrue(assembler.isRegistered(Assembly1.self))
Expand Down Expand Up @@ -135,7 +141,7 @@ private struct Assembly2: ModuleAssembly {
}
}

// Mock implementaiton of Assembly2. Adds an extra dependency on Assembly3
// Mock implementation of Assembly2. Adds an extra dependency on Assembly3
private struct Assembly2Fake: AutoInitModuleAssembly {

func assemble(container: Container) {
Expand Down