Returning Conditional Responses from MockedWebService in Swift - ios

I am implementing UI Testing and need to mock a service so I don't call the service again and again and remove the dependency on the network call. So, I created a Mock called MockWebservice. It is implemented below:
class MockedWebservice: NetworkService {
func login(username: String, password: String, completion: #escaping (Result<LoginResponse?, NetworkError>) -> Void) {
completion(.success(LoginResponse(success: true)))
}
}
It works but as you can see it always returns success: true. How can I make this MockedWebservice return a different response. The MockWebservice is injected into the main app using the launchEnvironment for unit test. Here is the code in the actual SwiftUI App which creates a real web service or a mocked version.
class NetworkServiceFactory {
static func create() -> NetworkService {
let environment = ProcessInfo.processInfo.environment["ENV"]
if let environment = environment {
if environment == "TEST" {
return MockedWebservice()
} else {
return Webservice()
}
} else {
return Webservice()
}
}
}

Add some logic to your mocked service so that it responds differently depending on the username/password it receives
Something like:
class MockedWebservice: NetworkService {
func login(username: String, password: String, completion: #escaping (Result<LoginResponse?, NetworkError>) -> Void) {
if username == "success" {
completion(.success(LoginResponse(success: true)))
} else {
completion(.failure(SomeNetworkError()))
}
}
}
You can test for additional username values to simulate different responses.
I would probably make the mocked method a bit more realistic. Use an asyncAfter on a utility dispatch queue to simulate network latency and the fact that your completion handler probably wont be called on the main queue.
class MockedWebservice: NetworkService {
func login(username: String, password: String, completion: #escaping (Result<LoginResponse?, NetworkError>) -> Void) {
DispatchQueue.global(qos: .utility).asyncAfter(.now()+0.5) {
if username == "success" {
completion(.success(LoginResponse(success: true)))
} else {
completion(.failure(SomeNetworkError()))
}
}
}
}

Related

Mocking iOS Firebase Auth sign-in methods

This question is somewhat similar to Mock third party classes (Firebase) in Swift but different enough to warrant a new question, based on the answers to it.
I'm trying to mock the Auth/FIRAuth method signIn(withEmail email: String, password: String, completion: AuthDataResultCallback?) and am running into difficulties with trying to mock the AuthDataResultCallback object, mainly because it has a User property that I also want to mock. Unfortunately, I'm not able to create my own User or Auth objects because they've been marked as not having an available initializer in Swift.
I have an object (let's call it UserAuthenticationRepository) that's responsible for performing user authentication and database reads. I'd like to inject a Firebase auth object into it to do these things under the hood, but since I want to test this repository object I'd like to be able to inject a Firebase mock object when I go to unit test it.
What I want to do is something like this (simplified slightly for this question):
import FirebaseAuth
protocol FirebaseUserType {
var uid: String { get }
}
extension User: FirebaseUserType {}
protocol FirebaseAuthDataResultType {
var user: FirebaseUserType { get }
}
extension AuthDataResult: FirebaseAuthDataResultType {
var user: FirebaseUserType {
// This is where I'm running into problems because AuthDataResult expects a User object,
// which I also use in the UserAuthenticationRepository signIn(withEmail:) method
}
}
protocol FirebaseAuthenticationType {
func signIn(withEmail email: String, password: String, completion: ((FirebaseAuthDataResultType?, Error?) -> Void)?)
}
extension Auth: FirebaseAuthenticationType {
func signIn(withEmail email: String, password: String, completion: ((FirebaseAuthDataResultType?, Error?) -> Void)?) {
let completion = completion as AuthDataResultCallback?
signIn(withEmail: email, password: password, completion: completion)
}
}
protocol UserAuthenticationType {
func loginUser(emailAddress: String, password: String) -> Observable<User>
}
class UserAuthenticationRepository: UserAuthenticationType {
private let authenticationService: FirebaseAuthenticationType
private let disposeBag = DisposeBag()
init(authenticationService: FirebaseAuthenticationType = Auth.auth()) {
self.authenticationService = authenticationService
}
func loginUser(emailAddress: String, password: String) -> Observable<User> {
return .create { [weak self] observer in
self?.authenticationService.signIn(withEmail: emailAddress, password: password, completion: { authDataResult, error in
if let error = error {
observer.onError(error)
} else if let authDataResult = authDataResult {
observer.onNext(authDataResult.user)
}
})
return Disposables.create()
}
}
As noted above, I'm running into problems when I try to extend AuthDataResult to conform to my FirebaseAuthDataResultType protocol. Is it possible to do what I'm trying to do? I'd ultimately like to pass back a uid string in my Firebase authentication service when testing UserAuthenticationRepository.
I was eventually able to find a way to mock the Firebase Auth objects necessary for me, but I had to resort to subclassing the Firebase User object, adding new properties to it be used during testing (can't create a User object directly or mutate its properties), and then creating a struct which conforms to FirebaseAuthDataResultType which is initialized with a MockUser object during testing. The protocols and extensions I ended up needing are below:
protocol FirebaseAuthDataResultType {
var user: User { get }
}
extension AuthDataResult: FirebaseAuthDataResultType {}
typealias FirebaseAuthDataResultTypeCallback = (FirebaseAuthDataResultType?, Error?) -> Void
protocol FirebaseAuthenticationType {
func signIn(withEmail email: String, password: String, completion: FirebaseAuthDataResultTypeCallback?)
func signOut() throws
func addStateDidChangeListener(_ listener: #escaping AuthStateDidChangeListenerBlock) -> AuthStateDidChangeListenerHandle
func removeStateDidChangeListener(_ listenerHandle: AuthStateDidChangeListenerHandle)
}
extension Auth: FirebaseAuthenticationType {
func signIn(withEmail email: String, password: String, completion: FirebaseAuthDataResultTypeCallback?) {
let completion = completion as AuthDataResultCallback?
signIn(withEmail: email, password: password, completion: completion)
}
}
Below are the mock objects:
class MockUser: User {
let testingUID: String
let testingEmail: String?
let testingDisplayName: String?
init(testingUID: String,
testingEmail: String? = nil,
testingDisplayName: String? = nil) {
self.testingUID = testingUID
self.testingEmail = testingEmail
self.testingDisplayName = testingDisplayName
}
}
struct MockFirebaseAuthDataResult: FirebaseAuthDataResultType {
var user: User
}
An instance of my mock Firebase authentication service with stubs:
class MockFirebaseAuthenticationService: FirebaseAuthenticationType {
typealias AuthDataResultType = (authDataResult: FirebaseAuthDataResultType?, error: Error?)
var authDataResultFactory: (() -> (AuthDataResultType))?
func signIn(withEmail email: String, password: String, completion: FirebaseAuthDataResultTypeCallback?) {
// Mock service logic goes here
}
// ...rest of protocol functions
}
Usage (using RxSwift and RxTest):
func testLoginUserReturnsUserIfSignInSuccessful() {
let firebaseAuthService = MockFirebaseAuthenticationService()
let expectedUID = "aM1RyjpaZcQ4EhaUvDAeCnla3HX2"
firebaseAuthService.authDataResultFactory = {
let user = MockUser(testingUID: expectedUID)
let authDataResult = MockFirebaseAuthDataResult(user: user)
return (authDataResult, nil)
}
let sut = UserSessionRepository(authenticationService: firebaseAuthService)
let userObserver = testScheduler.createObserver(User.self)
sut.loginUser(emailAddress: "john#gmail.com", password: "123456")
.bind(to: userObserver)
.disposed(by: disposeBag)
testScheduler.start()
let user = userObserver.events[0].value.element as? MockUser
// Assert MockUser properties, events, etc.
}
If anyone has any better ideas of how this can be accomplished, please let me know!

How to abstract network model fetching

Instead of putting network model loading in every controller, I'm trying to abstract it in only one controller ModelLoader
Here is what I came up with so far:
protocol ModelLoaderDelegate: class {
func didFetch(model: Any)
func modelFailedToFetch(errorMessage: String)
}
final class ModelLoader<ModelType> {
let api: API
weak var delegate: ModelLoaderDelegate?
private let client = dependencies.client
init(api: API) {
self.api = api
}
func fetchModel() {
client.performRequest(api: api, decodeTo: ModelType.self, completion: { result in
switch result {
case .success(let value):
self.delegate?.didFetch(model: value)
case .failure(let error):
self.delegate?.modelFailedToFetch(errorMessage: error.localizedDescription)
}
})
}
}
The one thing I'm not able to do until now is to replace Any in the didFetch method of the ModelLoaderDelegate to be a generic-like parameter.
I tried to do it like this:
func didFetch<T>(info: T)
but in the implementer of the delegate:
func didFetch<T>(info: T) {
// I need a concerete type here not a generic
}
Couldn't find another approach.
Instead to do this via delegate, you can make the logic by closures:
final class ModelLoader<ModelType> {
let api: API
private let client = dependencies.client
init(api: API) {
self.api = api
}
func fetchModel<ModelType>(completion: #escaping (Result<ModelType, Error>) -> ()) {
client.performRequest(api: api, decodeTo: ModelType.self, completion: { result in
switch result {
case .success(let value):
completion(.success(value))
case .failure(let error):
completion(.failure(error))
}
})
}
}
I think with this approach will be much easier to handle response :)

Using Generics in completionHandler

I have a simple app, that communicates with server via TCP Socket using custom protocol. I want to achieve HTTP-like response-request behaviour, abstracting from socket layer.
So I have simple protocol:
protocol ResponseType {
init(with frame: SocketMessage)
}
And some of examples:
struct MessageAck: ResponseType {
var messageId: String
init(with frame: SocketMessage) {
messageId = frame.messageId
}
}
I created simple protocol for sending requests:
protocol APIClient {
func send<T: ResponseType>(request: SocketAPIRequest, completion: ((Result<T>) -> Void)?)
}
enum SocketAPIRequest {
case textMessage(messageId: String, ...)
...
}
And finally:
enum Result<T> {
case success(T)
case failure(Error)
}
class SocketAPIClient: APIClient {
typealias MessageId = String
private var callbacks = [Receipt: ((Result<ResponseType>) -> Void)]()
...
func send<T>(request: SocketAPIRequest, completion: ((Result<T>) -> Void)?) where T : ResponseType {
....
callbacks[stompFrame.receiptId] = completion
....
}
}
So, when I want to store callback for each request, to call it after answer will be received, I got such error:
Cannot assign value of type '((Result<T>) -> Void)?' to type '((Result<ResponseType>) -> Void)?'
I guess the problem with mixing Type's and objects, or maybe something else.
Swift generics are not covariant (with special hard-coded exceptions for Array which involve copying the elements). That means that Result<Apple> is not a subtype of Result<Fruit>. See Swift Generics & Upcasting for examples of why.
In your case, what would prevent you from passing a Result<MessageBody> to a callback that expected a Result<MessageAck>? For example:
for callback in callbacks {
callback(result)
}
How could you know this was legal at compile time for any given type of result?
EDIT (BETTER ANSWER):
You can hide the type inside a closure to get what you want. Try this:
class SocketAPIClient: APIClient {
typealias MessageId = String
private var callbacks = [Receipt: ((Result<SocketMessage>) -> Void)]() // <--- Change
func send<T>(request: SocketAPIRequest, completion: ((Result<T>) -> Void)?) where T : ResponseType {
// Store the closure we don't understand inside a closure we do
callbacks[stompFrame.receiptId] = { result in
switch result {
case .success(let message):
completion?(.success(T.init(with: message)))
case .failure(let error):
completion?(.failure(error))
}
}
}
}
Now, instead of trying to hold T directly in callbacks, it's held in each individual closure, hidden from the rest of the class, and T never escapes this function. When you get to wherever you call callback in your code, just pass it the Result<SocketMessage> that I assume you already have somewhere.
OLD ANSWER:
The simplest solution to your problem is to have the callback always pass a Result<Data> and remove T entirely:
protocol APIClient {
func send(request: SocketAPIRequest, completion: ((Result<Data>) -> Void)?)
}
Then leave it to the MessageAck (in the completion handler) to deserialize itself from the raw data.
There are other ways to achieve all this with type erasers, but they're much more complex and sometimes very fiddly.
Have you tried the following signature
func send<T:ResponseType>(request: SocketAPIRequest, completion: ((Result<T>) -> Void)?){ ... }
and still getting error?
Edit 1:
or probably you should try something like this
protocol APIClient {
associatedtype T
func send(request: SocketAPIRequest, completion: ((Result<T>) -> Void)?)
}
and,
class SocketAPIClient: APIClient {
typealias MessageId = String
typealias T = ResponseType
private var callbacks = [Receipt: ((Result<ResponseType>) -> Void)]()
...
func send(request: SocketAPIRequest, completion: ((Result<T>) -> Void)?) {
....
callbacks[stompFrame.receiptId] = completion
....
}
}

Writing API requests with completion blocks using Swift generics

I am experimenting with generics in Swift and I am attempting to push it to its limits.
In my application I have a super simple API wrapper around Alamofire. The structure is like so:
API -> Request -> Alamofire request
Here is some generic code that I threw into a playground to test some concepts. Here is what I have so far:
protocol SomeProtocol {
var cheese: String { get }
init()
}
class Something: SomeProtocol {
required init() { }
var cheese: String {
return "wiz"
}
}
class API {
class func performRequest<T: SomeProtocol>(completion: (T?, NSError) -> Void) {
// This code is irrelevant, just satisfying the completion param
let test = T()
let error = NSError(domain: "Pizza", code: 1, userInfo: nil)
completion(test, error)
}
}
func test() {
API.performRequest<Something> { item, error in
}
}
Calling the function gives the error:
"Cannot explicitly specialize a generic function"
****** UPDATE ******
As per the answer below, removing the typical <> generic type specifier and instead adding the expected type to the completion params solves the issue. Just a quick example:
func test() {
API.performRequest { (item: Something?, error) in
}
}
Additionally, I have discovered that making the API wrapper class a generic class solves the issue like so:
protocol SomeProtocol {
var pizza: String { get }
}
class SomeObject: SomeProtocol {
var pizza: String { return "pie" }
}
class API<T: SomeProtocol> {
class func performRequest(completion: (T?, NSError?) -> Void) {
}
}
func test() {
API<SomeObject>.performRequest { item, error in
// Do something with item, which has a type of SomeObject
}
}
Either way, the end goal is accomplished. We have a single generic method that will perform a set of tasks and return, via completion closure, the object based on the type passed in with each use.
The way generics work is they allow a function to use unspecialized variables inside of its implementation. One can add functionality to these variables by specifying that the variables must conform to a given protocol (this is done within the declaration). The result is a function that can be used as a template for many types. However, when the function is called in the code itself, the compiler must be able to specialize and apply types to the generics.
In your code above, try replacing
func test() {
API.performRequest<Something> { item, error in
}
}
with
func test() {
API.performRequest { (item: Something?, error) in
}
}
this lets the compiler know which type it must apply to the function without explicitly specifying. The error message you received should now make more sense.
Here is what i did using alamofire and alamofire object mapper:
Step 1: Create modal classes that conforms to Mappable protocols.
class StoreListingModal: Mappable {
var store: [StoreModal]?
var status: String?
required init?(_ map: Map){
}
func mapping(map: Map) {
store <- map["result"]
status <- map["status"]
}
}
Step 2: Create a fetch request using the generic types:
func getDataFromNetwork<T:Mappable>(urlString: String, completion: (T?, NSError?) -> Void) {
Alamofire.request(.GET, urlString).responseObject { (response: Response<T, NSError>) in
guard response.result.isSuccess else{
print("Error while fetching: \(response.result.error)")
completion(nil, response.result.error)
return
}
if let responseObject = response.result.value{
print(responseObject)
completion(responseObject, nil)
}
}
}
Step 3: Now all you need is to call this fetch function. This can be done like this:
self.getDataFromNetwork("your url string") { (userResponse:StoreListingModal?, error) in
}
You will not only get your response object but it will also be mapped to your modal class.

Dealing with Closures - Make code more generic

There are two functions as shown below. Most of the functionality is the same in both. Its idea is to get the output of the webservice from getResponse() [Helper Callback], parse and pass the info to wrapper call back through getResult().
static func getAllDealers(dealerSearchServiceDomain: ARSDealerSearchServiceDomain, wrapperCallback:(getResult: () throws -> Void) -> Void) throws
{
try ARSInputValidator.validateZipCode(dealerSearchServiceDomain.zip)
try ARSDealerConnection.getAllDealers(dealerSearchServiceDomain, helperCallback: { (getResponse) -> Void in
do
{
let result = try getResponse()
try ARSDealerParser.parseDealerSearchResponse(dealerSearchServiceDomain)
wrapperCallback(getResult: { return })
}
catch
{
wrapperCallback(getResult: { throw error })
}
})
}
static func getDealerDetails(dealerDetailsServiceDomain: ARSDealerDetailsServiceDomain, wrapperCallback:(getResult: () throws -> Void) -> Void) throws
{
try ARSDealerConnection.getDealerDetails(dealerDetailsServiceDomain, helperCallback: { (getResponse) -> Void in
do
{
let result = try getResponse()
try ARSDealerParser.parseDealerDetailsResponse(dealerDetailsServiceDomain)
wrapperCallback(getResult: { return })
}
catch
{
wrapperCallback(getResult: { throw error })
}
})
}
I am trying to add a separate function for the common functionality like,
static func parser(serviceCallDomain: ARSServiceCallDomain ,wrapperCallback:(getResult:() throws -> String) -> Void, helperCallback:(getResponse:() throws -> String) -> Void) throws
{
helperCallback { (getResponse) -> Void in
But there is a compilation error & i am not able to complete it. There are 15+ web service calls, so a common shown as i am trying will be very helpful.
Next step, i also need to pass the functions parseDealerSearchResponse() & parseDealerDetailsResponse() to the common function.
I am new to closures. Kindly help.
//EDIT -- ADDING SAMPLE
I have a sample for the problem in Git - Refer class Layer1.swift
https://github.com/vivinjeganathan/ErrorHandling/tree/Closures-Refactor
I think the best you can do to refactor the code is to define a function that handles some of the common functionality like parsing and validation and that ultimately calls the completion closure back to the controller, something like this:
static func handleResponse(parser: Parser, validator: Validator, getResult: () throws -> AnyObject, completion: (getParsedResult: () throws -> AnyObject) -> Void) {
do
{
let result = try getResult()
let parsedObject = try parser.parse(result)
try validator.validate(parsedObject)
completion(getParsedResult: { return parsedObject })
}
catch
{
completion(getParsedResult: { throw error })
}
}
notice that it receives the parser, validator, the closure that captures the result from the layer below and the completion closure that belongs to the final user (usually the View Controller), and then this function could be used like this:
static func getAllDealers(dealerSearchServiceDomain: AnyObject, wrapperCallback:(getResult: () throws -> AnyObject) -> Void) throws {
let validator = DealersValidator() // create real validator
let parser = DealersParser() // create real parser
try validator.validate(dealerSearchServiceDomain)
try ARSDealerConnection.getAllDealers(dealerSearchServiceDomain, helperCallback: { (getResponse) -> Void in
self.handleResponse(parser, validator: validator, getResult: getResponse, completion: wrapperCallback)
})
}
in this case handleResponse lives in the same class with getAllDealers but it can actually be a global function that every service can call.
I think that it might be possible to write a better implementation using generics but it wouldn't be much shorter than this, in the end you can't save yourself from creating the validators and parsers and call the next layer.

Resources