Unable to cast to protocol from framework during test cases - swift

So I have a class that comes from one of our internal frameworks. It is defined as follows:
// This lives within a framework
class ExternalClass: ExternalClassProtocol {
// implementation here
}
// This lives within my test target
class MockExternalClass: ExternalClassProtocol {
// Mock implementation here
}
// This lives within the same external frame work as ExternalClass
protocol ExternalClassProtocol: AnyObject {
// Protocol methods here
}
During my test cases, if I try to cast MockExternalClass as? ExternalClassProtocol, the test case crashes.
However, during live app runtime, there is no problem casting ExternalClass as? ExternalClassProtocol.
Is this an issue with trying to implement a protocol from an external module? Is there a way around this?
The class is being accessed through dependency injection (see below dependency injection implementation). The crash occurs on the resolve function.
If you actually debug to this point, you can see that the mock dependency IS in my dependency root (the services array below).
So I know its not failing to cast due to the dependency being missing.
#propertyWrapper
struct Injected<Value> {
var key: String
var wrappedValue: Value {
get { return Dependency.root.resolve(key: self.key) }
set { Dependency.root.add(key: self.key, newValue) }
}
init(key: String) {
self.key = key
}
}
class Dependency {
static let root = Dependency()
var services: [String : Any] = [:]
func add<T>(key: String, _ service: T) {
services[key] = service
}
func resolve<T>(key: String) -> T {
guard let component: T = services[key] as? T else {
// The test crashes here. It works fine on other mocks that are internal to the project
fatalError("Dependency '\(T.self)' not resolved!")
}
return component
}
func clearDependencies() {
self.services.removeAll()
}
private init() {}
}
In my test case:
#testable import MyProject
import ExternalDependency
class TestCase: XCTestCase {
private var subject: ClassWithService!
private var mockInternalClass: MockInternalClassProtocol!
private var mockExternalClass: MockInternallClassProtocol!
func setUp() {
mockExternalClass = MockExternalClass() // This one crashes when trying to cast to its parent protocol
mockInternalClass = MockInternalClass() // This one does not crash when casting to parent protocol.
Dependency.root.add(key: "internal_class", mockInternalClass)
Dependency.root.add(key: "external_class", mockExternalClass)
}
}
Some things I've tried:
Adding AnyObject to the protocol (this fixed a similar issue for internally defined classes that I mock).
changing mockExternalClass type to be the protocol
changing mockExternalClass type to be the implementation
Aside from one protocol being defined in one of our pods, there is no difference between the external protocol and the one we have defined in our own project.
One thing I have noticed is that the cast does not fail if you set a break point inside one of my test case functions. But if you try the same cast within the Dependency.resolve function it crashes. Which leads me to believe there is an issue with the generics.
Any ideas?

Related

How to mock a generic implementation of a protocol?

I'm trying to do some dependency injection in order to unit test, and also make iOS live preview easier.
I have a Store protocol, and I want to use these in my SUT classes so I can pass mock implementations.
protocol Store {
associatedtype AnyData
func load() -> AnyData?
func save(data anyData: AnyData)
}
class FileStore<AnyData: Codable>: Store {
func load() -> AnyData? { /* Actual code that saves to a file */ }
func save(data anyData: AnyData) { /* Actual code that saves to a file */ }
}
class MockStore<AnyData: Codable>: Store {
func load() -> AnyData? { /* Returns mocked value for unit testing */ }
func save(data anyData: AnyData) { /* Returns mocked value for unit testing */ }
}
However, in my SUT class I have the following:
class MyClassToBeTested {
// THIS DOESN'T WORK
let bookStore: Store // ERROR: Protocol 'Store' can only be used as a generic
// constraint because it has Self or associated type requirements
// DOESN'T WORK EITHER
let studentStore: Store<Student> // ERROR: Cannot specialize non-generic type 'Store'
}
// in real app
MyClassToBeTested(
bookStore: FileStore<Book>()
studentStore: FileStore<Student>()
)
// in test or preview
MyClassToBeTested(
bookStore: MockStore<Book>()
studentStore: MockStore<Student>()
)
Seems like I'm stuck. I'm essentially trying to have a generic protocol, similar to a generic interface in Java. What am I missing?
UPDATE
Following #Jessy answer, I did:
class MyClassToBeTested<BookStore: Store, StudentStore: Store>
where
BookStore.AnyData == Book,
StudentStore.AnyData == Student
{
let bookStore: BookStore
let studentStore: StudentStore
Which solves half of the issue. The other half is how to type a variable with it:
class OtherClass {
var sut: MyClassToBeTested // ERROR: Generic parameter Bookstore could not be inferred
var sut2: MyClassToBeTested< // I know this isn't supported in Swift
Store<Book>, // but can't figure out the right syntax
Store<Student> // ERROR: Cannot specialize non-generic type 'Store'
> // ERROR: Protocol 'Store' as a type cannot conform to the protocol itself
var sut3: MyClassToBeTested< // Compiles, BUT I cannot pass a
FileStore<Book>, // MockStore in my tests/preview
FileStore<Student> // so it's useless
>
}
The Store<Student> syntax has never been supported, but there has been a lot of talk on the Swift forum about it lately—it may be soon.
There are many other Q/A's on Stack Overflow about not being able to use a protocol with an associated type like an existential—it's finally available but only in Xcode 13.3 (beta).
It seems to me that you want things constrained like this:
class MyClassToBeTested<BookStore: Store, StudentStore: Store>
where
BookStore.AnyData == Book,
StudentStore.AnyData == Student
{
let bookStore: BookStore
let studentStore: StudentStore
If not, the latest syntax, available starting in Xcode 13.3, is
class MyClassToBeTested<StudentStore: Store>
where StudentStore.AnyData == Student {
let bookStore: any Store
let studentStore: StudentStore
init(
bookStore: any Store,
studentStore: StudentStore
) {
self.bookStore = bookStore
self.studentStore = studentStore
}
}

Swift Generics - Protocol does not conform to Protocol

I'm trying to get a container that implements a set of protocols that i pass as parameter to a function on the original container.
struct Container {
let someProperty: String
let otherProperty: String
}
// Subcontainers
protocol Base {}
protocol SomePropertyContainer: Base {
var someProperty: String { get }
}
protocol OtherPropertyContainer: Base {
var otherProperty: String { get }
}
extension Container: SomePropertyContainer, OtherPropertyContainer {}
// Sub Container Provisioning Protocol
protocol SubContainerProviderProtocol {
func subContainer<T: Base>(protos: T.Type) -> T?
}
extension Container: SubContainerProviderProtocol {
func subContainer <T: Base>(protos: T.Type) -> T? {
return self as? T
}
}
// Example container
let subContainerProvider: SubContainerProviderProtocol = Container(someProperty: "Why does this not work!", otherProperty: "Seriously.")
Getting this up and running would allow me to inject the ContainerProviderProtocol into consumers while giving them the possibility to specify themselves which SubContainer they actually want.
E.g. a class that would be interested in only the someProperty could look like this
// Example Container Provider consumer
class SomeClass {
let subContainerProvider: SubContainerProviderProtocol
init(subContainerProvider: SubContainerProviderProtocol) {
self.subContainerProvider = subContainerProvider
}
func printSomeProperty() {
let someProperty = subContainerProvider
.subContainer(protos: SomePropertyContainer.self)?
.someProperty
print(someProperty)
}
}
// Example call
let someClass = SomeClass(subContainerProvider: subContainerProvider)
someClass.printSomeProperty() // "Why does this not work!"
This solution would be incredible for dependency injection & testability.
However the restriction T: Base is causing the compiler error
In argument type 'SomePropertyContainer.Protocol', 'SomePropertyContainer' does not conform to expected type 'Base'
Not specifying conformance to Base will compile, but would also allow to pass any type as T.
I've tried with associated types within an additional protocol etc, however have not figured it out. And while this issue is incredibly fun, I'm running out of ideas.
Possibly related to (but not exactly same) https://bugs.swift.org/browse/SR-55
Here's the problem: at some point you have to start working with actual types, and not just protocols. Your line:
func container<T: Base>(protos: T.Type) -> T?
is telling the compiler that you're going to give this function a type, generically T, that conforms to the protocol Base, not another protocol. You need something like this:
class SPC: SomePropertyContainer {
var someProperty: String = ""
}
class SomeClass {
let containerProvider: ContainerProviderProtocol
init(containerProvider: ContainerProviderProtocol) {
self.containerProvider = containerProvider
}
func printSomeProperty() {
let someProperty = containerProvider
.container(protos: SPC.self)?
.someProperty
print(someProperty)
}
}
SPC is a type that conforms to the SomePropertyContainer protocol, which itself conforms to the Base protocol, so this is what your code is expecting.

Cannot convert return expression of type 'NodeType.NodeType?' to return type 'NodeType?'

I have been dealing with this problem in Swift for a while, tried type erasure and all kinds of stuff, but to no avail; and the problem seems to be common sense (at least to me). How can I emulate abstract class in Swift? In other words, how to make this work:
import Foundation
protocol NodeProtocol
{
associatedtype NodeType
func getNode() -> NodeType
}
class Node: NodeProtocol
{
typealias NodeType = Node
var next:Node = Node()
func getNode() -> Node {
return next
}
}
class AbstractGraph<NodeType:NodeProtocol>
{
var root: NodeType?
func getRoot () -> NodeType?{
return root?.getNode()
}
}
Note: this is just some random code I wrote, just to get to the core of the problem.
I'm not sure if I understood your question correctly (you actually ask a couple) but here's the way I 'emulate' abstract classes in Swift (4):
protocol Node {
func getNext() -> Node
}
extension Node {
func getNext() -> Node {
fatalError("getNext is not implemented!")
}
}
struct StringNode: Node {
var data: String!
}
let sNode = StringNode(data: "test-node-1")
sNode.getNext()
Using an extension on the protocol I decide which functions need to be implemented by using a fatalError() call.
Now every Node descendant of Node must implement getNext() otherwise we crash. Run the above code in a playground and you will see that it will crash on the last line, forcing you to implement getNext().
The 'fix' would be:
protocol Node {
func getNext() -> Node
}
extension Node {
func getNext() -> Node {
fatalError("getNext is not implemented!")
}
}
struct StringNode: Node {
var data: String!
func getNext() -> Node {
return StringNode(data: "test-node-2")
}
}
let sNode = StringNode(data: "test-node-1")
sNode.getNext()
And now it won't crash on the last line. All tested in playgrounds using Xcode 9 and Swift 4.
Note: this is not the best implementation of this Node protocol ofcourse. The protocol should list operable methods which should denote a Node called something like NodeOperatable. A class called Node should be created implementing this NodeOperatable and not implementing the getNext() which will force descendant classes to implement the getNext().
Try this:
class AbstractGraph<T: NodeProtocol> {
var root: T?
func getRoot () -> T.NodeType? {
return root?.getNode()
}
}
It doesn't matter what you call the type. In the above I've named it T. In yours, you have named it NodeType as in:
class AbstractGraph<NodeType: NodeProtocol>
But reusing NodeType as a type name was just kind of confusing with the associatedtype NodeType name in NodeProtocol that one would expect both to be same but they're actually not.
Basically your AbstractGraph requires a type, say T, that conforms to NodeProtocol.
Since this type T conforms to NodeProtocol, it automatically has a NodeType property in it.
Furthermore, getNode() as per the protocol requirements is to return this NodeType property.
Hence in getRoot() since you are basically returning what getNode() is returning, you are required to return the type T's NodeType property, not the type T itself.

Is it possible use KVO with generic types in swift4

My method receive a generic type in parameters which associated with protocol. Inside this method I use additional NSObject class to wrap/hide generic type because KVO only works with ObjC classes.
An example is given below:
protocol OutputData: NSObjectProtocol {
associatedtype Output
var dataContainer: DataContainer<Output>? { get set }
}
class DataContainer<O>: NSObject {
var outputData: O?
}
func injectResult<O: OutputData>(from: O) {
let dataCont = from.dataContainer
observer = dataCont?.observe(\.outputData) { (object, change) in
let data = object.outputData as! Data
self.inputData = data
}
}
In this situation I don't see any compile time errors but if I try to run this code I get the next exception:
Could not extract a String from KeyPath
Swift.ReferenceWritableKeyPath<Concurrency.DataContainer<Foundation.Data>,
Swift.Optional<Foundation.Data>>:
file /Library/Caches/com.apple.xbs/Sources/swiftlang/swiftlang-900.0.65.2/src/swift/stdlib/public/SDK/Foundation/NSObject.swift,
line 85
Any help will be appreciated.

Instantiate class from protocol type

I am writing method which takes a type which conforms to a protocol and instantiates an instance of this class. When I build it, the compiler crashes with a segfault. I appreciate that this points to a compiler bug 99% of the time, but I am interested to see if what I'm trying to do is logically correct or am I just throwing absolute nonsense at the compiler and I shouldn't be surprised to see it crash.
Here is my code
protocol CreatableClass {
init()
}
class ExampleClass : CreatableClass {
required init() {
}
}
class ClassCreator {
class func createClass(classType: CreatableClass.Type) -> CreatableClass {
return classType()
}
}
ClassCreator.createClass(ExampleClass.self)
I also tried to rule out passing a Type as a method parameter as being the root of the problem and the following code also crashes the compiler:
protocol CreatableClass {
init()
}
class ExampleClass : CreatableClass {
required init() {
}
}
let classType: CreatableClass.Type = CreatableClass.self
let instance = classType()
So - is this just a straightforward compiler bug and does what I am trying to do seem reasonable, or is there something in my implementation that is wrong?
Edit:
This can be achieved using generics as shown #Antonio below but unfortunately i believe that isn't useful for my application.
The actual non-dumbed down use-case for doing this is something like
protocol CreatableClass {}
protocol AnotherProtocol: class {}
class ClassCreator {
let dictionary: [String : CreatableClass]
func addHandlerForType(type: AnotherProtocol.Type, handler: CreatableClass.Type) {
let className: String = aMethodThatGetsClassNameAsAString(type)
dictionary[className] = handler()
}
required init() {}
}
I usually do that by defining a generic method. Try this:
class func createClass<T: CreatableClass>(classType: T.Type) -> CreatableClass {
return classType()
}
Update
A possible workaround is to pass a closure creating a class instance, rather than passing its type:
class ClassCreator {
class func createClass(instantiator: () -> CreatableClass) -> (CreatableClass, CreatableClass.Type) {
let instance = instantiator()
let classType = instance.dynamicType
return (instance, classType)
}
}
let ret = ClassCreator.createClass { ExampleClass() }
The advantage in this case is that you can store the closure in a dictionary for example, and create more instances on demand by just knowing the key (which is something in 1:1 relationship with the class name).
I used that method in a tiny dependency injection framework I developed months ago, which I realized it works only for #objc-compatible classes only though, making it not usable for my needs...