I'm trying to unit test a class of my own which is calling a method on a third party class:
FIRAuth.auth()?.signInAnonymously() { (user, error) in
//
}
I'm using protocol based dependency injection to achieve this:
protocol FIRAuthProtocol {
func signInAnonymously(completion: FIRAuthResultCallback?)
}
extension FIRAuth: FIRAuthProtocol {}
class MyClass {
private var firAuth: FIRAuthProtocol
init(firAuth: FIRAuthProtocol) {
self.firAuth = firAuth
}
func signIn() {
firAuth.signInAnonymously() { (user, error) in
//
}
}
}
class MockFIRAuth: FIRAuthProtocol {
var signInAnonymouslyCalled = false
func signInAnonymously(completion: FIRAuthResultCallback? = nil) {
signInAnonymouslyCalled = true
}
}
class MyClassSpec: QuickSpec {
override func spec() {
describe("MyClass") {
describe(".signIn()") {
it("should call signInAnonymously() on firAuth") {
let mockFIRAuth = MockFIRAuth()
let myClass = MyClass(firAuth: mockFIRAuth)
expect(mockFIRAuth.signInAnonymouslyCalled).to(beFalse())
myClass.signIn()
expect(mockFIRAuth.signInAnonymouslyCalled).to(beTrue())
}
}
}
}
}
So far so good!
Now, I'd like my mockFIRAuth to return an instance of FIRUser.
Here's my issue: I can't create an instance of FIRUser myself.
FYI: public typealias FIRAuthResultCallback = (FIRUser?, Error?) -> Swift.Void
If found this great article which explains how to make a method on a third party class return a protocol instead of a type. http://masilotti.com/testing-nsurlsession-input/
Maybe my situation is different than the article's, but here's my shot at this:
I've defined a FIRUserProtocol:
protocol FIRUserProtocol {
var uid: String { get }
}
extension FIRUser: FIRUserProtocol {}
I've updated my FIRAuthProtocol to call the completion handler with FIRUserProtocol instead of FIRUser:
protocol FIRAuthProtocol {
func signInAnonymously(completion: ((FIRUserProtocol?, Error?) -> Void)?)
}
I've updated my FIRAuth extension to support the modified protocol. My newly defined method calls the default implementation of signInAnonymously:
extension FIRAuth: FIRAuthProtocol {
func signInAnonymously(completion: ((FIRUserProtocol?, Error?) -> Void)? = nil) {
signInAnonymously(completion: completion)
}
}
Finally, I've updated MockFIRAuth to support the modified protocol:
class MockFIRAuth: FIRAuthProtocol {
var signInAnonymouslyCalled = false
func signInAnonymously(completion: ((FIRUserProtocol?, Error?) -> Void)? = nil) {
signInAnonymouslyCalled = true
}
}
Now, when I run my test everything comes to a crashing halt:
Thread 1: EXC_BAD_ACCESS (code=2, address=0x7fff586a2ff8)
Please advice!
Update
After renaming the completion argument label in my FIRAuthProtocol's method everything seems to work as expected:
protocol FIRAuthProtocol {
func signInAnonymously(completionWithProtocol: ((FIRUserProtocol?, Error?) -> Void)?)
}
extension FIRAuth: FIRAuthProtocol {
func signInAnonymously(completionWithProtocol: ((FIRUserProtocol?, Error?) -> Void)? = nil) {
signInAnonymously(completion: completionWithProtocol)
}
}
This solves my issue for now, but I'd still like to know why my first attempt was unsuccessful. Does this mean that the two methods with different parameter types in their closures can't be told apart, which was causing my app to crash?
I've finally found an elegant way to solve this.
protocol FIRAuthProtocol {
func signInAnonymously(completion: ((FIRUserProtocol?, Error?) -> Void)?)
}
extension FIRAuth: FIRAuthProtocol {
func signInAnonymously(completion: ((FIRUserProtocol?, Error?) -> Void)? = nil) {
let completion = completion as FIRAuthResultCallback?
signInAnonymously(completion: completion)
}
}
This way, there's no need to alter function names or argument labels.
Related
I am stuck with this situation where I have a custom JSONDecoder struct which contains a private function to decode data, and another function which is exposed, and should return a specific, Decodable type. I would like these functions to throw successively so I only have to write my do/catch block inside the calling component, but I'm stuck with this error on the exposedFunc() function:
Invalid conversion from throwing function of type '(Completion) throws -> ()' (aka '(Result<Data, any Error>) throws -> ()') to non-throwing function type '(Completion) -> ()' (aka '(Result<Data, any Error>) -> ()')
Here is the code:
import Foundation
import UIKit
typealias Completion = Result<Data, Error>
let apiProvider = ApiProvider()
struct DecodableTest: Decodable {
}
struct CustomJSONDecoder {
private static func decodingFunc<T: Decodable>(
_ response: Completion,
_ completion: #escaping (T) -> Void
) throws {
switch response {
case .success(let success):
try completion(
JSONDecoder().decode(
T.self,
from: success
)
)
case .failure(let error):
throw error
}
}
static func exposedFunc(
value: String,
_ completion: #escaping (DecodableTest) -> Void
) throws {
apiProvider.request {
try decodingFunc($0, completion)
}
}
}
class CustomViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
do {
try CustomJSONDecoder.exposedFunc(value: "test_value") { result in
// Do something with result
}
} catch {
print(error)
}
}
}
class ApiProvider: NSObject {
func request(_ completion: #escaping (Completion) -> ()) {
}
}
Thank you for your help
This defines method that takes a non-throwing function:
class ApiProvider: NSObject {
func request(_ completion: #escaping (Completion) -> ()) {
}
}
So in all cases, this function must take a Completion and return Void without throwing. However, you pass the following:
apiProvider.request {
try decodingFunc($0, completion)
}
This method does throw (note the uncaught try), so that's not allowed. You need to do something if this fails:
apiProvider.request {
do {
try decodingFunc($0, completion)
} catch {
// Here you must deal with the error without throwing.
}
}
}
Not using concurrency features is making your code hard to understand. Switch!
final class CustomViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
Task {
let result = DecodableTest()
// Do something with result
}
}
}
extension DecodableTest {
init() async throws {
self = try JSONDecoder().decode(Self.self, from: await APIProvider.data)
}
}
enum APIProvider {
static var data: Data {
get async throws { .init() }
}
}
I need to change this generic class that accepts an array to something that is not an array
class GenericDataSource<T> : NSObject {
var data: DynamicValue<[T]> = DynamicValue([])
//var data: DynamicValue<T>?
}
When I remove the square brackets I get an error
typealias CompletionHandler = (() -> Void)
class DynamicValue<T> {
var value : T {
didSet {
self.notify()
}
}
private var observers = [String: CompletionHandler]()
init(_ value: T) {
self.value = value
}
public func addObserver(_ observer: NSObject, completionHandler: #escaping CompletionHandler) {
observers[observer.description] = completionHandler
}
public func addAndNotify(observer: NSObject, completionHandler: #escaping CompletionHandler) {
self.addObserver(observer, completionHandler: completionHandler)
self.notify()
}
private func notify() {
observers.forEach({ $0.value() })
}
deinit {
observers.removeAll()
}
}
You need an init in GenericDataSource
class GenericDataSource<T> {
var data: DynamicValue<T>
init(_ value: T) {
data = DynamicValue(value)
}
}
or make data optional
class GenericDataSource<T> : NSObject {
var data: DynamicValue<T>?
}
And then use them like
let gds = GenericDataSource("Hello")
or for the optional variant
let gds = GenericDataSource<String>()
gds.data = DynamicValue("Hello")
I am trying to write test cases for the test target. The app is running perfectly, but the test case failing with the mocking.
Here is my code, please let me know if there any other solutions. How do I mock services?
Is there any other way to write a test case for the ViewCoontroller initializer?
class NavigationCodeTests: XCTestCase {
var subject: ViewController?
override func setUp() {
self.subject = ViewController(service: MockUserService())
_ = self.subject?.view
}
func test_user_service_not_nil() {
XCTAssertNotNil(self.subject?.service, "User service can't be nil after initialization of ViewController")
}
func test_user_service_should_have_user() {
self.subject?.userViewModel?.user.observe(on: self, observerBlock: { (user) in
XCTAssertNotNil(user?.name, "User service can't be nil after initialization of ViewController")
})
}
}
class MockUserService: UserService {
func fetch(_ completion: #escaping(_ user: User) -> Void) {
completion(User(name: "abc", contact: "124"))
}
}
class UserService: UserServiceDelegate {
func fetch(_ completion: #escaping(_ user: User) -> Void) {
completion(User(name: "Damu", contact: "12"))
}
}
protocol UserServiceDelegate {
func fetch(_ completion: #escaping(_ user: User) -> Void)
}
I am new to Swift, but I have plenty of experience in other languages like Java, Kotlin, Javascript, etc. It's possible that what I want to do is not supported by the language, and I've poured over the Swift Language Guide looking for the answer.
I want to implement the decorator pattern, using generics. I easily did this in Kotlin, and I'm porting the library to Swift.
class Result<T> {
let result: T?
let error: NSError?
init(result: T?, error: NSError?) {
self.result = result
self.error = error
}
}
protocol DoSomething {
associatedtype T
func doSomething() -> Result<T>
}
protocol StoreSomething {
associatedtype T
func storeSomething(thing: Result<T>)
}
/*
* DOES NOT COMPILE
*/
class StoringSomething<T> {
private let delegate: DoSomething
private let store: StoreSomething
init(delegate: DoSomething, store: StoreSomething) {
self.delegate = delegate
self.store = store
}
func doSomething() -> Result<T> {
let result = delegate.doSomething()
store.storeSomething(thing: result)
return result
}
}
I get a Protocol 'DoSomething' can only be used as a generic constraint because it has Self or associated type requirements error from the compiler. I've tried using a typealias and other ideas from SO and the Swift manual.
Thanks to #Sweeper's suggestion on associatedtype erasure you can implement the Decorator pattern with generics like so:
class AnyDoSomething<T>: DoSomething {
func doSomething() -> Result<T> {
fatalError("Must implement")
}
}
class AnyStoreSomething<T>: StoreSomething {
func storeSomething(thing: Result<T>) {
fatalError("Must implement")
}
}
class StoringSomething<T>: DoSomething {
private let delegate: AnyDoSomething<T>
private let store: AnyStoreSomething<T>
init(delegate: AnyDoSomething<T>, store: AnyStoreSomething<T>) {
self.delegate = delegate
self.store = store
}
func doSomething() -> Result<T> {
let result = delegate.doSomething()
store.storeSomething(thing: result)
return result
}
}
class DoSomethingNice<T>: AnyDoSomething<T> {
override func doSomething() -> Result<T> {
}
}
I'm currently writing and database access class for two database APIs (realm and Firestore). With the intention to slim down the code i try to solve the whole thing a little sleaker with generics (#1). Unfortunately, it's not working. Where do I miss the point?
I tried with defining associatedtypes (#2) and setting them within the RealmAccessStragy class. But on this point the compiler returns error if try to access the protocol via the PersistenceController.
I am grateful for any help!
APPROACH #1
enum DataResult<T> {
case success(T)
case failure(Error)
}
protocol DataApiAccess: AnyObject {
func read<U, T>(primaryKey: U, completion: #escaping ((DataResult<T>) -> Void))
}
class RealmAccessStrategy {
...
func read<U, T>(primaryKey: U, completion: #escaping ((DataResult<T>) -> Void)) {
guard let realmObject = realmInstance.object(ofType: realmObjectType, forPrimaryKey: primaryKey) else {
completion(.failure(RealmAccessError.noObject))
return
}
completion(.success(realmObject)) // ERROR: Member 'success' in 'DataResult<_>' produces result of type 'DataResult<T>', but context expects 'DataResult<_>'
}
}
// Later implementation
class PersistenceController {
private let strategy: DataApiAccess
init(use: DataApiAccess) {
self.strategy = use
}
func load<U, T>(primaryKey: U, completion: #escaping ( (DataResult<T>) -> Void ) ) {
strategy.read(primaryKey: primaryKey, completion: completion)
}
}
🆘 ERROR: Member 'success' in 'DataResult<>' produces result of type 'DataResult', but context expects 'DataResult<>'
APPROACH #2
enum DataResult<T> {
case success(T)
case failure(Error)
}
protocol DataApiAccess {
associatedtype ReturnType
func read(primaryKey: PrimaryKeyType, completion: #escaping DataApiHandler<ReturnType>)
}
class RealmAccessStrategy: DataApiAccess {
...
// Typealias
internal typealias ReturnType = Object
func read(primaryKey: Any, completion: #escaping ((DataResult<Object>) -> Void)) {
guard let realmObject = realmInstance.object(ofType: realmObjectType, forPrimaryKey: primaryKey) else {
completion(.failure(RealmAccessError.noObject))
return
}
completion(.success(realmObject))
}
}
class PersistenceController {
private let strategy: DataApiAccess // ERROR: Protocol 'DataApiAccess' can only be used as a generic constraint because it has Self or associated type requirements
init(use: DataApiAccess) {
self.strategy = use
}
...
}
}
🆘 ERROR: Protocol 'DataApiAccess' can only be used as a generic constraint because it has Self or associated type requirements
You cannot set variable generic protocols but you can set methods
Example code below
Create enum for base result:
enum DataResult<T> {
case success(T)
case failure(Error)
}
///Set a protocol generic methods:
protocol DataApiAccess {
func read<T: Codable>(primaryKey: PrimaryKeyType, completion: #escaping (DataResult<T>) -> Void)
}
class RealmAccessStrategy: DataApiAccess {
func read<T: Codable>(primaryKey: PrimaryKeyType, completion: #escaping (DataResult<T>) -> Void) {
// Read data from database
}
}
class NetworkAccessStrategy: DataApiAccess {
func read<T: Codable>(primaryKey: PrimaryKeyType, completion: #escaping (DataResult<T>) -> Void) {
// Get data from request
}
}
class PersistenceController {
private let strategy: DataApiAccess
init(use: DataApiAccess) {
// Set dependency inversion for offline or online state
self.strategy = use
}
func foo() {
// TODO
//strategy.read(primaryKey: <#T##PrimaryKeyType#>, completion: <#T##(DataResult<Decodable & Encodable>) -> Void#>)
}
}
Enjoy!