I am trying to unit test simple HttpClient behaviour. For that I have created an GenericHttpClientInterface protocol and concrete class GenericHttpClient that implements this protocol.
protocol GenericHttpClientInterface {
func makeRequest<T: Decodable>(request: URLRequest) -> Observable<T>
}
class GenericHttpClient: GenericHttpClientInterface {
func makeRequest<T: Decodable>(request: URLRequest) -> Observable<T> {
return URLSession.shared.rx.data(request: request).jsonDecode(to: T.self)
}
}
What I wanted to achieve is to mock that class :
class MockHttpClient: GenericHttpClientInterface {
var invokedMakeRequestCount = 0
var invokedMakeRequestParameters: (request: URLRequest, Void)?
var stubbedMakeRequestResult: Observable<Any>!
func makeRequest<T: Decodable>(request: URLRequest) -> Observable<T> {
invokedMakeRequestCount += 1
invokedMakeRequestParameters = (request, ())
return stubbedMakeRequestResult as! Observable<T>;
}
}
What gives ma a problem is that the method I am mocking has generic parameter T inside which is a class that the request will be decoded to. I don't know this parameter until I call this function so basically in MockHttpClient class for a property that stores stub data for makeRequest I've created:
stubbedMakeRequestResult: Observable<Any>
and after returning it I'm trying to cast that to result type Observable. This gives me an warning
Cast from 'Observable<Any>?' to unrelated type 'Observable<T>' always fails
and in consequence
Thread 1: signal SIGABRT.
Any idea how to stub that data?
Example test that creates SIGABRT:
class GenericHttpTest: XCTestCase {
var sut: Repository!
var mockHttpClient: MockHttpClient!
override func setUp() {
mockHttpClient = MockHttpClient()
sut = Repository(httpClient: mockHttpClient)
}
let test_mocked_data_stub = DataModelStruct(args: DataModelStruct.InsideModelStruct(foo1: "bar"))
func test_should_return_mocked_data_from_mock_http_client() {
mockHttpClient.stubbedMakeRequestResult = Observable.just(test_mocked_data_stub)
let response = try! sut.getFooBar().toBlocking().first()
XCTAssertEqual(response, test_mocked_data_stub)
}
}
Due to the fact that generics are invariant in Swift, Observable<Any> will never ever be convertible to Observable<T>, unless T is Any. This is what cause your crash, as when you assign the value to stubbedMakeRequestResult, the concrete Observable is converted to Observable<Any>, and there's no turning point from here.
What you can do to avoid this is to make stubbedMakeRequestResult an Any, as this will not make any conversions behind the scenes. A small problem is that you loose the type safety and inference, but you can fix this stubbing via a function:
class MockHttpClient: GenericHttpClientInterface {
...
var stubbedMakeRequestResult: Any!
func stubMakeRequestResult<T>(_ result: Observable<T>) {
stubbedMakeRequestResult = result
}
...
}
Related
In my iOS project, I want to do the following:
create an Encodable class (named ChannelAnswer) which has multiple attributes, including another generic Encodable object
pass an instance of that class as an argument of a function or return it from a function
Here is what I tried:
class ChannelAnswer<T> : Encodable where T : Encodable
{
let errorCode: String?
let data: T?
let origin: Int = 2
init(_ errorCode: String?, _ data: T? = nil)
{
self.errorCode = errorCode
self.data = data
}
}
Now, if I want to return an instance of that class from a function, like the following:
func test() -> ChannelAnswer
{
...
return ChannelAnswer("abc", anyEncodableObject)
}
I get the following error:
Reference to generic type 'ChannelAnswer' requires arguments in <...>
The thing is: the data attribute could be of any type, I only know that that type is Encodable (the test()function above is just an example for the sake of simplicity).
So how can I create my ChannelAnswer class and successfully pass it as an argument of a function or return it from a function?
Thanks.
What you need is a generic method.
func test<T: Encodable>(data: T) -> ChannelAnswer<T> {
// ...
return ChannelAnswer("abc", data)
}
I am trying to create a variable of a generic function type:
import Foundation
typealias Processor<T:Decodable> = (T.Type, URL) -> Promise<T>
struct Service {
let processor: Processor
func getAPI1() -> Promise<TypedResponse1> {
let url = ...
return processor(TypedResponse1.self, url)
}
func getAPI2(params: [String]) -> Promise<TypedResponse2> {
let url = ...
return processor(TypedResponse2.self, url)
}
}
Compiling gives me an error:
Reference to generic type 'Processor' requires arguments in <...>
However, I don't want to specialize the variable since it kinda beats the purpose of having it and will be more difficult to test.
How can I accomplish this?
I have generic method to create object that extend protocol FromResponse.
extension FromResponse {
static func object<T>(_ response: [String: Any]?) -> T? where T: FromResponse, T: NSObject {
guard let response = response else { return nil }
let obj: T = T()
return obj
}
}
So whenever I want to call it from anywhere in a code there is no issue. Let's say:
let myObject: MyObject? = MyObject.object(response)
Work's perfectly. But sometimes I'm getting array of objects from my response so I would like to have generic parser as well:
static func objects<T>(_ response: [[String: Any]]?) -> [T]? where T: FromResponse, T: NSObject {
guard let response = response else { return nil }
var returnArray: [T] = [T]()
for singleResponse in response {
if let object: T = T.object(singleResponse) {
returnArray.append(object)
}
}
return returnArray
}
So I expect from this method to return array of MyObject, but In fact I'm getting compiler error when I'm calling this:
let myObjects: [MyObject]? = MyObject.objects(response)
It says:
Generic parameter 'T' could not be inferred
Well, I know what does it mean but I did specify type, so this error should not happen. Also when I do this:
var typ: [MyObject] = [MyObject]()
for singleResponse in (response as? [[String: Any]])! {
let pack: MyObject? = MyObject.object(singleResponse)
typ.append(pack!)
}
It works!
Why? How to have parser that returns array of generics objects?
I don't know for sure why Swift says “Generic parameter 'T' could not be inferred”, but my guess is it has to do with array covariance.
What's covariance? Consider this:
class Base { }
class Sub: Base { }
func f(_ array: [Base]) { }
Can you pass an [Sub] to f? In Swift, you can. Because Sub is a subtype of Base, [Sub] is a subtype of [Base]. (This is called “covariance”.) So you can pass a [Sub] anywhere that a [Base] is allowed:
f([Sub]())
// No errors.
And you can return a [Sub] where a [Base] is expected:
func g() -> [Base] { return [Sub]() }
// No errors.
And you can assign a [Sub] to a [Base] variable:
let bases: [Base] = [Sub]()
// No errors.
So back to your code:
static func objects<T>(_ response: [[String: Any]]?) -> [T]? ...
let myObjects: [MyObject]? = MyObject.objects(response)
Certainly MyObject.objects(_:) must return a type that can be treated as [MyObject]?. But any subtype of [MyObject]? is also acceptable. The type is not tightly constrained. I guess this is why Swift doesn't like it.
The fix is to tell Swift explicitly what type you want, using a pattern you'll see in many places in the Swift standard library:
static func objects<T>(ofType type: T.Type, from response: [[String: Any]]?) -> [T]? ...
// Note that you might not actually have to use the `type` parameter
// in the method definition.
let myObjects = MyObject.objects(ofType: MyObject.self, from: response)
It's not clear why this method is on the MyObject class at all. Perhaps you should make it a method on [[String: Any]]:
extension Collection where Element == [String: Any] {
func objects<T>(ofType type: T.Type) -> [T]? ...
}
let myObjects = response.objects(ofType: MyObject.self)
I have the following function in a class in my program:
func getXMLForTrips(atStop: String, forRoute: Int, completionHandler: #escaping (String) -> Void) {
let params = [api key, forRoute, atStop]
Alamofire.request(apiURL, parameters: params).responseString { response in
if let xmlData = response.result.value {
completionHandler(xmlData)
} else {
completionHandler("Error")
}
}
}
In the init() for the class, I attempt to call it like this:
getXMLForTrips(atStop: stop, forRoute: route) { xmlData in
self.XMLString = xmlData
}
This compiles without errors, but after init() is executed, my class's self.XMLString is still nil (shown both by the Xcode debugger and by my program crashing due to the nil value later on). I see no reason why this shouldn't work. Can anyone see what I am missing?
You shouldn't be making internet calls in the initializer of a class. You will reach the return of the init method before you go into the completion of your internet call, which means it is possible that the class will be initialized with a nil value for the variable you are trying to set.
Preferably, you would have another class such as an API Client or Data Source or View Controller with those methods in it. I am not sure what your class with the init() method is called, but lets say it is called Trips.
class Trips: NSObject {
var xmlString: String
init(withString xml: String) {
xmlString = xml
}
}
Then one option is to put the other code in whatever class you are referencing this object in.
I'm gonna use a view controller as an example because I don't really know what you are working with since you only showed two methods.
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
//setting some fake variables as an example
let stop = "Stop"
let route = 3
//just going to put that method call here for now
getXMLForTrips(atStop: stop, forRoute: route) { xmlData in
//initialize Trip object with our response string
let trip = Trip(withString: xmlData)
}
}
func getXMLForTrips(atStop: String, forRoute: Int, completionHandler: #escaping (String) -> Void) {
let params = [api key, forRoute, atStop]
Alamofire.request(apiURL, parameters: params).responseString { response in
if let xmlData = response.result.value {
completionHandler(xmlData)
} else {
completionHandler("Error")
}
}
}
}
If you want to be able to initialize the class without requiring setting the xmlString variable, you can still do the same thing.
Change the Trips class init() method to whatever you need it to be and set var xmlString = "" or make it optional: var xmlString: String?.
Initialize the class wherever you need it initialized, then in the completion of getXMLForTrips, do trip.xmlString = xmlData.
I know it is possible to pass class type to a function in swift:
func setGeneric<T>(type: T.Type){ }
setGeneric(Int.self)
But how we can return type from function? Writing something like
func getGeneric<T>() -> T.Type {
return Int.self
}
gives compiler error "Int is not identical to T". So is it possible to return type from a swift function?
Edit
Some explanation. I have classes that are used for persistence (I'm using Realm) and I have classes that acts as wrappers around this classes. All wrappers inherits from RealmClassWrapper which needs to know what Realm class it actually wraps. So lets say I have this realm model:
class RealmTodo: RLMObject {
dynamic var title = ""
}
and my wrappers supper class looks like this:
class RealmClassWrapper {
private let backingModel: RLMObject
//...
func backingModelType<T>() -> T.Type{ fatalError("must be implemented") }
}
and actual wrapper:
class Todo: RealmClassWrapper {
//some other properties
func backingModelType<T>() -> T.Type{ return RealmTodo.self }
}
You can return any type you want.
func getTypeOfInt() -> Int.Type { return Int.self }
func getTypeOfBool() -> Bool.Type { return Bool.self }
If the type is not determined from arguments or if the return is constant, there is no need to introduce a generic T type.
It works when I modify your function like this:
func getGeneric<T>(object: T) -> T.Type {
return T.self
}
getGeneric(0) // Swift.Int
You can force the downcast (as!) as below
func getGeneric<T>() -> T.Type {
return Int.self as! T.Type
}
But out of the function scope, you need to indicate the returned type:
var t:Int.Type = getGeneric()
Yes, this is possible. The problem here is that you say your function returns a generic T.type, but you always return Int.type. Since T is not always an Int, the compiler raises an error.
If you don't want to specify the return type you can use AnyClass as it instead of a template parameter.
class A {}
class B {}
public enum ExampleEnum: String {
case a
case b
func asClass() -> AnyClass {
switch self {
case .a:
return A.self
case .b:
return B.self
}
}
}
let myGoal : AnyClass = ExampleEnum.a.asClass()
You can also avoid the final cast to AnyClass, but compiler will show you an error