I am trying to accomplish protocol oriented programming in Swift 3 using generics. Is this not fully supported yet? I'm going to show you what I would LIKE to do below but will not compile. Am I missing something here? My goal is to be able to use protocol oriented programming to perform dependency injection with the intent of easily mocking these structures in my unit tests.
protocol ZombieServiceProtocol {
func fetchZombies()
var zombieRepository: RepositoryProtocol<Zombie> { get set }
}
struct ZombieService: ZombieServiceProtocol {
var zombieRepository: RepositoryProtocol<Zombie>
init(zombieRepository: RepositoryProtocol<Zombie>) {
self.zombieRepository = zombieRepository
}
func fetchZombies() {
self.zombieRepository.deleteAll()
self.createFakeZombies()
}
private func createFakeZombies() {
for index in 1...100 {
let zombie = Zombie(id: index, name: "Zombie \(index)")
self.zombieRepository.insert(zombie)
}
}
}
The Zombie class looks like this:
public struct Zombie: Persistable {
var id: Int
let name: String?
init(id: Int, name: String?) {
self.id = id
self.name =name
}
}
Its Persistable protocol looks like this:
protocol Persistable {
var id: Int { get set }
}
And my Repository code looks something like this:
protocol RepositoryProtocol: class {
associatedtype Object: Persistable
//...
func insert(_ object: Object) -> Void
func deleteAll(_ predicate: (Object) throws -> Bool) -> Void
}
class Repository<Object: Persistable>: RepositoryProtocol {
var items = Array<Object>()
//...
func insert(_ object: Object) {
self.items.append(object)
}
func deleteAll() {
self.items.removeAll()
}
}
I get the following error in my ZombieServiceProtocol:
I get the following error in my ZombieService:
And to highlight exactly what I'm trying to accomplish, here is what a simple test would look like in which I create a Mock repository and attempt to use that instead of the real one in my ZombieService:
@testable import ZombieInjection
class ZombieServiceTests: XCTestCase {
private var zombieRepository: RepositoryProtocol<Zombie>!
private var zombieService: ZombieServiceProtocol
override func setUp() {
super.setUp()
// Put setup code here. This method is called before the invocation of each test method in the class.
self.zombieRepository = RepositoryMock<Zombie>()
self.zombieService = ZombieService(zombieRepository: self.zombieRepository)
}
override func tearDown() {
// Put teardown code here. This method is called after the invocation of each test method in the class.
super.tearDown()
}
func testExample() {
// Arrange
// Act
self.zombieService.fetchZombies()
// Assert
XCTAssert(self.zombieRepository.count() > 0)
}
}
This code also does not compile presently with the same errors as above.
I have been looking at the associatedTypes and typeAlias tags as well as the Generics Manifesto. While looking at the Manifesto, I believe this falls into the "Generic Protocols" section which is currently marked as Unlikely (which is bumming me out). If I can't accomplish something like I'm trying to do above, what would be the next best solution?
Dependency Injection is a software design pattern in which an object receives other instances that it depends on. It's a commonly used technique that allows reusing code, insert mocked data, and simplify testing.
A Dependency Injection Container is an object that is responsible for recording, deciding, and settling all the dependencies. Responsibilities of the deciding and settling mean that the DI container needs to know about the constructor arguments and the relationships between the objects.
Initializer-Based Injection There are three common types of dependency injection: setter-, interface-, and constructor-based. From those three, the constructor-based is the preferable one. In the iOS world, it could be called the initializer-based injection.
The answer to your question is yes it is definitely possible, just that it currently requires some PAT-related "magic". With Swift3 and Xcode 8.0 beta 4, you should be able to run the following in a playground:
protocol Persistable {
var id: Int { get set }
}
protocol RepositoryProtocol: class {
associatedtype Object: Persistable
func insert(_ object: Object) -> Void
func deleteAll()
}
protocol ZombieServiceProtocol {
associatedtype RepositoryType: RepositoryProtocol
var zombieRepository: RepositoryType { get set }
func fetchZombies()
}
public struct Zombie: Persistable {
var id: Int
let name: String?
}
// Mocks
class RepositoryMock<Object: Persistable>: RepositoryProtocol {
func insert(_ object: Object) { print("look, there's another one!")}
func deleteAll() { print("it's safe out there, all zombies have been deleted") }
}
struct ZombieServiceMock<RepositoryType: RepositoryProtocol
where RepositoryType.Object == Zombie>: ZombieServiceProtocol {
var zombieRepository: RepositoryType
init(zombieRepository: RepositoryType) {
self.zombieRepository = zombieRepository
}
func fetchZombies() {
self.zombieRepository.deleteAll()
self.createMockZombies()
}
private func createMockZombies() {
for index in 1...5 {
let zombie = Zombie(id: index, name: "Zombie \(index)")
self.zombieRepository.insert(zombie)
}
}
}
// Tests
class ZombieServiceTests<RepositoryType: RepositoryProtocol,
ServiceType: ZombieServiceProtocol
where ServiceType.RepositoryType == RepositoryType> {
private var zombieRepository: RepositoryType
private var zombieService: ServiceType
init(repository: RepositoryType, service: ServiceType) {
zombieRepository = repository
zombieService = service
}
func testExample() {
self.zombieService.fetchZombies()
}
}
let repositoryMock = RepositoryMock<Zombie>()
let repositoryService = ZombieServiceMock(zombieRepository: repositoryMock)
let zombieTest = ZombieServiceTests(repository: repositoryMock, service: repositoryService)
zombieTest.testExample()
// Prints:
// it's safe out there, all zombies have been deleted
// look, there's another one!
// look, there's another one!
// look, there's another one!
// look, there's another one!
// look, there's another one!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With