Files
Dip/Tests/DipTests/AutoInjectionTests.swift
2020-05-29 14:42:25 +01:00

506 lines
14 KiB
Swift

//
// Dip
//
// Copyright (c) 2015 Olivier Halligon <olivier@halligon.net>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import XCTest
@testable import Dip
private protocol Server: AnyObject {
var client: Client! {get}
var anotherClient: Client! {get set}
}
private protocol Client: AnyObject {
var server: Server? {get}
var anotherServer: Server! {get set}
}
#if swift(>=5.1)
private class ServerImp: Server {
@InjectedWeak(didInject: { _ in
AutoInjectionTests.clientDidInjectCalled = true
}) var client: Client!
@InjectedWeak(required: false) var _optionalProperty: AnyObject?
weak var anotherClient: Client!
}
private class ClientImp: Client {
@Injected(didInject: { _ in
AutoInjectionTests.serverDidInjectCalled = true
}) var server: Server
@Injected(required: false) var _optionalProperty: AnyObject?
@Injected(tag: "tagged") var taggedServer: Server
@Injected(tag: nil) var nilTaggedServer: Server
var anotherServer: Server!
}
#else
private class ServerImp: Server {
var _client = InjectedWeak<Client>() { _ in
AutoInjectionTests.clientDidInjectCalled = true
}
var client: Client! {
return _client.value
}
weak var anotherClient: Client!
var _optionalProperty = InjectedWeak<AnyObject>(required: false)
}
private class ClientImp: Client {
var _server = Injected<Server>() { _ in
AutoInjectionTests.serverDidInjectCalled = true
}
var server: Server? {
return _server.value
}
var anotherServer: Server!
var _optionalProperty = Injected<AnyObject>(required: false)
var taggedServer = Injected<Server>(tag: "tagged")
var nilTaggedServer = Injected<Server>(tag: nil)
}
#endif
#if swift(>=5.1)
private class Obj1 {
@InjectedWeak var obj2: Obj2?
@Injected var obj3: Obj3?
}
private class Obj2 {
@Injected var obj1: Obj1?
}
#else
private class Obj1 {
let obj2 = InjectedWeak<Obj2>()
let obj3 = Injected<Obj3>()
}
private class Obj2 {
let obj1 = Injected<Obj1>()
}
#endif
private class Obj3 {
weak var obj1: Obj1?
init(obj: Obj1) {
self.obj1 = obj
}
}
class AutoInjectionTests: XCTestCase {
static var serverDidInjectCalled: Bool = false
static var clientDidInjectCalled: Bool = false
let container = DependencyContainer()
override func setUp() {
container.reset()
}
func testThatItResolvesAutoInjectedDependencies() {
container.register { ServerImp() as Server }
container.register { ClientImp() as Client }
let client = try! container.resolve() as Client
let server = client.server
XCTAssertTrue(client === server?.client)
}
func testThatItResolvesInheritedDependencies() {
class ServerImp2: ServerImp {
#if swift(>=5.1)
@InjectedWeak(didInject: { _ in
XCTAssertTrue(AutoInjectionTests.serverDidInjectCalled, "Inherited properties should be resolved first")
}) var client2: Client?
#else
var _client2 = InjectedWeak<Client>() { _ in
XCTAssertTrue(AutoInjectionTests.serverDidInjectCalled, "Inherited properties should be resolved first")
}
var client2: Client? {
return _client2.value
}
#endif
}
container.register { ServerImp2() as Server }
container.register { ClientImp() as Client }
//when
let client = try! container.resolve() as Client
let server = client.server as? ServerImp2
XCTAssertTrue(client === server?.client)
XCTAssertTrue(client === server?.client2)
}
func testThatItCanSetInjectedProperty() {
container.register { ServerImp() as Server }
container.register { ClientImp() as Client }
let client = (try! container.resolve() as Client) as! ClientImp
let server = client.server as! ServerImp
let newServer = ServerImp()
let newClient = ClientImp()
#if swift(>=5.1)
client.server = newServer
server.client = newClient
#else
client._server = client._server.setValue(newServer)
server._client = server._client.setValue(newClient)
#endif
XCTAssertTrue(client.server === newServer)
XCTAssertTrue(server.client === newClient)
}
func testThatItThrowsErrorIfFailsToAutoInjectDependency() {
container.register { ClientImp() as Client }
XCTAssertThrowsError(try self.container.resolve() as Client)
}
func testThatItResolvesAutoInjectedSingletons() {
//given
container.register(.singleton) { ServerImp() as Server }
container.register(.singleton) { ClientImp() as Client }
//when
let sharedClient = try! container.resolve() as Client
let sharedServer = try! container.resolve() as Server
let client = try! container.resolve() as Client
let server = client.server
//then
XCTAssertTrue(client === sharedClient)
XCTAssertTrue(client === server?.client)
XCTAssertTrue(server === sharedServer)
}
func testThatItCallsResolveDependencyBlockWhenAutoInjecting() {
var serverBlockWasCalled = false
//given
container.register { ServerImp() as Server }
.resolvingProperties { (container, server) -> () in
serverBlockWasCalled = true
}
var clientBlockWasCalled = false
container.register { ClientImp() as Client }
.resolvingProperties { (container, client) -> () in
clientBlockWasCalled = true
}
//when
let _ = try! container.resolve() as Client
XCTAssertTrue(serverBlockWasCalled)
let _ = try! container.resolve() as Server
XCTAssertTrue(clientBlockWasCalled)
}
func testThatItReusesResolvedAutoInjectedInstances() {
//given
container.register { ServerImp() as Server }
.resolvingProperties { (container, server) -> () in
server.anotherClient = try! container.resolve() as Client
}
container.register { ClientImp() as Client }
.resolvingProperties { (container, client) -> () in
client.anotherServer = try! container.resolve() as Server
}
//when
let client = try! container.resolve() as Client
//then
let server = client.server
let anotherServer = client.anotherServer
XCTAssertTrue(server === anotherServer)
let oneClient = server!.client
let anotherClient = server!.anotherClient
XCTAssertTrue(oneClient === anotherClient)
XCTAssertTrue(client === anotherClient)
}
func testThatItReusesAutoInjectedInstancesOnNextResolveOrAutoInjection() {
//given
container.register { Obj1() }
container.register { Obj2() }
container.register { Obj3(obj: try self.container.resolve()) }
//when
let obj2 = try! container.resolve() as Obj2
//then
#if swift(>=5.1)
XCTAssertTrue(obj2 === obj2.obj1!.obj2!,
"Auto-injected instance should be reused on next auto-injection")
XCTAssertTrue(obj2.obj1! === obj2.obj1!.obj3!.obj1,
"Auto-injected instance should be reused on next resolve")
#else
XCTAssertTrue(obj2 === obj2.obj1.value!.obj2.value!,
"Auto-injected instance should be reused on next auto-injection")
XCTAssertTrue(obj2.obj1.value! === obj2.obj1.value!.obj3.value!.obj1,
"Auto-injected instance should be reused on next resolve")
#endif
}
func testThatThereIsNoRetainCycleBetweenAutoInjectedCircularDependencies() {
//given
container.register { ServerImp() as Server }
container.register { ClientImp() as Client }
//when
var client: Client? = try! container.resolve() as Client
//then
weak var weakServer: Server? = client?.server
weak var weakClient = client
XCTAssertNotNil(weakClient)
XCTAssertNotNil(weakServer)
client = nil
XCTAssertNil(weakClient)
XCTAssertNil(weakServer)
}
func testThatItCallsDidInjectOnAutoInjectedProperty() {
AutoInjectionTests.clientDidInjectCalled = false
AutoInjectionTests.serverDidInjectCalled = false
//given
container.register { ServerImp() as Server }
container.register { ClientImp() as Client }
//when
let _ = try! container.resolve() as Client
//then
XCTAssertTrue(AutoInjectionTests.clientDidInjectCalled)
XCTAssertTrue(AutoInjectionTests.serverDidInjectCalled)
}
func testThatNoErrorThrownWhenOptionalPropertiesAreNotAutoInjected() {
//given
container.register { ServerImp() as Server }
container.register { ClientImp() as Client }
XCTAssertNoThrow(
try container.resolve() as Client,
"Container should not throw error if failed to resolve optional auto-injected properties."
)
}
func testThatItResolvesTaggedAutoInjectedProperties() {
//given
container.register { ServerImp() as Server }
container.register(tag: "tagged") { ServerImp() as Server }
container.register { ClientImp() as Client }
//when
let client = try! container.resolve() as Client
//then
#if swift(>=5.1)
let taggedServer = (client as! ClientImp).taggedServer!
#else
let taggedServer = (client as! ClientImp).taggedServer.value!
#endif
let server = client.server!
//server and tagged server should be resolved as different instances
XCTAssertTrue(server !== taggedServer)
XCTAssertNotNil(server)
XCTAssertNotNil(taggedServer)
}
func testThatItPassesTagToAutoInjectedProperty() {
//given
container.register { ServerImp() as Server }
container.register(tag: "tagged") { ServerImp() as Server }
container.register { ClientImp() as Client }
//when
let client = try! container.resolve(tag: "tagged") as Client
//then
#if swift(>=5.1)
let taggedServer = (client as! ClientImp).taggedServer!
#else
let taggedServer = (client as! ClientImp).taggedServer.value!
#endif
let server = client.server!
//server and tagged server should be resolved as the same instance
XCTAssertTrue(server === taggedServer)
}
func testThatItDoesNotPassTagToAutoInjectedPropertyWithExplicitTag() {
//given
container.register { ServerImp() as Server }
container.register(tag: "tagged") { ServerImp() as Server }
container.register { ClientImp() as Client }
.resolvingProperties { (container, client) -> () in
client.anotherServer = try! container.resolve() as Server
}
//when
let client = try! container.resolve(tag: "otherTag") as Client
//then
#if swift(>=5.1)
let taggedServer = (client as! ClientImp).taggedServer!
let nilTaggedServer = (client as! ClientImp).nilTaggedServer!
#else
let taggedServer = (client as! ClientImp).taggedServer.value!
let nilTaggedServer = (client as! ClientImp).nilTaggedServer.value!
#endif
let server = client.server!
//server and tagged server should be resolved as different instances
XCTAssertTrue(server !== taggedServer)
XCTAssertTrue((client.anotherServer as! ServerImp) === nilTaggedServer)
XCTAssertNotNil(server)
XCTAssertNotNil(taggedServer)
XCTAssertNotNil(nilTaggedServer)
}
struct Foo
{
struct Bar
{
}
}
struct Baz
{
struct Bar
{
}
}
func testScopedTypes() {
let key1 = DefinitionKey(type: Baz.Bar.self, typeOfArguments: Void.self)
let key2 = DefinitionKey(type: Foo.Bar.self, typeOfArguments: Void.self)
XCTAssertNotEqual(key1, key2)
XCTAssertNotEqual(key1.hashValue, key2.hashValue)
container.register { Baz.Bar() }
XCTAssertNotNil(try? container.resolve() as Baz.Bar)
XCTAssertThrowsError(try container.resolve() as Foo.Bar)
container.register { Foo.Bar() }
XCTAssertNotNil(try? container.resolve() as Foo.Bar)
}
func testThatItAutoInjectsPropertyWithCollaboratingContainer() {
let collaborator = DependencyContainer()
collaborator.register { ServerImp() as Server }
container.register { ClientImp() as Client }
container.collaborate(with: collaborator)
collaborator.collaborate(with: container)
let client = try! container.resolve() as Client
let server = client.server
XCTAssertTrue(client === server?.client)
}
func testThatItDoesNotAutoInjectIfDisabledInDefinition() {
container.register { ServerImp() as Server }
container.register { ClientImp() as Client }
.autoInjectingProperties(false)
let client = try! container.resolve() as Client
let server = client.server
XCTAssertNil(server)
}
func testThatItDoesNotAutoInjectIfDisabledInContainer() {
let container = DependencyContainer(autoInjectProperties: false)
container.register { ServerImp() as Server }
container.register { ClientImp() as Client }
let client = try! container.resolve() as Client
let server = client.server
XCTAssertNil(server)
}
func testThatItAutoInjectsWhenOverriddenInDefinition() {
let container = DependencyContainer(autoInjectProperties: false)
container.register { ServerImp() as Server }
container.register { ClientImp() as Client }
.autoInjectingProperties(true)
let client = try! container.resolve() as Client
let server = client.server
XCTAssertNotNil(server)
XCTAssertNil(server?.client)
}
}