Mocking in Swift for XCTest - ios

I am writing test cases for my project which is mix up with Objective C as well as Swift code. I am aware about OCMock framework which I have used previously for mocking/Stubbing for writing Test cases in Objective C.
But I googled and found that it doesn't support fully for swift, since it is based on Objective C runtime.
I am trying to write test cases in swift language. Is there way I can do mocking/Stubbing for service level layer. For eg.
func getPersonData(id:String, success: (ReponseEnum) -> Void, failure: (error: NSError) -> Void) {
let requestPara:NSDictionary = ["id": id]
let manager: MyRequestManager = MyRequestManager.sharedManager()
//MyRequestManager is nothing but AFNetworking class
let jsonRequest
/// Service request creation code here
// Service Call
manager.POST(url, parameters: jsonRequest, success: { (task: NSURLSessionDataTask!, responseObject: AnyObject!) -> () in
// Some business logic
//success block call
success (successEnum)
}) {(task: NSURLSessionDataTask!, error: NSError!) -> () in
// failure block call
failure (failureEnum)
}
}
Here how to mock post method call for dummy responseObject So I can write test cases?

You need to use dependency injection to be able to mock the POST method.
Your class, where you defined the getPersonData(id:success:failure) method, needs to accept MyRequestManager as a parameter in constructor:
class MyClass {
private let requestManager: MyRequestManager
init(requestManager: MyRequestManager) {
self.requestManager = requestManager
}
}
Then you create a mock for your request manager:
class MockMyRequestManager: MyRequestManager {
// not sure about correct method signature
override func POST(url: NSURL, parameters: [String: AnyObject], success: (() -> Void)?) {
//implement any custom logic that you want to expect when executing test
}
}
And in the tests you initialise your class with a mock:
let myClass = MyClass(requestManager: MockMyRequestManager())
You can find more details about dependency injection here:
http://martinfowler.com/articles/injection.html

Related

Testing dependency injection

I am using Dependency Inversion the functional way (ie. using closure instead of protocol/interface/pure virtual) like so:
class StatusPoller {
init(api: #escaping (String, String) -> Observable<Data>,
getAccessToken: #escaping () -> String) {
...
}
}
class HeartbeatSender {
init(api: #escaping (String, String) -> Observable<Data>,
getAccessToken: #escaping () -> String) {
...
}
}
class Api {
func pollStatus(id: String, accessToken: String) -> Observable<Data> {
...
}
func sendHeartbeat(id: String, accessToken: String) -> Observable<Data> {
...
}
}
Now this is the problem. In the dependency injection resolver (Swinject), it goes like this:
container.register(StatusPoller.self) { _ in
let api = SwinjectApi.container.resolve(Api.self)!
let authStore = SwinjectAuth.container.resolve(AuthStore.self)!
return StatusPoller(api: api.pollStatus(id:accessToken:),
getAccessToken: authStore.getAccessToken)
}
container.register(HeartbeatSender.self) { _ in
let api = SwinjectApi.container.resolve(Api.self)!
let authStore = SwinjectAuth.container.resolve(AuthStore.self)!
return StatusPoller(api: api.pollStatus(id:accessToken:),
getAccessToken: authStore.getAccessToken)
}
I accidentally used the same dependency for the API member of different classes. It, fortunately (or unfortunately depending on your POV), worked fine for almost a year now since the backend doesn't really check for the heartbeat consistency (or at all). However, there's a new feature in the last few months that required the checking of the heartbeat of the client all the time now and this caused a bug. I've fixed it after I noticed that the heartbeat is not actually being sent at all because of that mistake above.
So my question is: How do I prevent that in the future? Testing the dependency injection? But, how though? I still don't get how I can make sure that I have injected the correct dependency.
Thanks
EDIT:
To make the question more clear, it's basically the equivalent of this but in functional:
Suppose I have an interface like this:
protocol VoipHandlerInterface {
...
}
class TwilioVoipHandlerAdapter: VoipHandlerInterface {
...
}
class AsteriskVoipHandlerAdapter: VoipHandlerInterface {
...
}
class VoipManagerInteractor {
let voipHandler: VoipHandlerInterface
init(voipHandler: VoipHandlerInterface) {
self.voipHandler = voipHandler
}
}
How do I test / make sure that, for instance, I use Twilio instead of Asterisk in VoipManagerInteractor?
That's the problem with dependency injection. Your unit tests can check if the dependency is used correctly but they can't check to ensure that the correct dependency is passed in. Only integration tests can do that.
The best you can do here is create a UI Testing Bundle for your app and check some minimal functionality to ensure that the correct dependency is passed in. Try to minimize the number of integration tests you need though because they are brittle and slow.
Lastly, instead of dependency injection, attempt dependency rejection. Make your boundaries value types instead of interfaces.

Passing concrete instance to closure that expects a protocol parameter

In my app, I have a function to call a specific API endpoint of mine, and that function accepts a closure as a completion handler. That closure accepts a Result of my custom Decodable type (Category) in the success case, and an Error in the failure case. Altogether, its method signature looks like this:
static func getAllRestaurantCategories(completion: #escaping (Result<[Category], Error>) -> Void) -> Void
This function calls out to Alamofire to determine the languages that the server supports, and then get the list of all possible restaurant categories. It's implemented like this:
static func getAllRestaurantCategories(completion: #escaping (Result<[Category], Error>) -> Void) -> Void{
API.localizedRequest(API.categories) { (request: DataRequest) in
request.responseDecodable(of: [Category].self) { (response: DataResponse<[Category], AFError>) in
completion(response.result)
}
}
}
However, on the line with completion(response.result), I get a compiler error that says Cannot convert value of type 'Result<[Category], AFError>' to expected argument type 'Result<[Category], Error>'. This error goes away if I change the closure my method accepts to accept an AFError in the failure case, like this:
static func getAllRestaurantCategories(completion: #escaping (Result<[Category], AFError>) -> Void) -> Void{
API.localizedRequest(API.categories) { (request: DataRequest) in
request.responseDecodable(of: [Category].self) { (response: DataResponse<[Category], AFError>) in
completion(response.result)
}
}
}
Alamofire's AFError conforms to Error, so it seems to me that this should work just fine. I know I can parse Alamofire's Result myself and generate my own to pass to my completion handler, but I'd rather not write all that extra custom code if I don't have to. How can I get the type system to understand that this should be ok?
Simply put, in (at least the current version of) Swift, if Sub is a subtype of Base, that doesn't mean that Container<Sub> is a subtype of Container<Base>.
In fact, Container<Sub> and Container<Base> are unrelated types.
So, while we can do the following:
protocol Car {}
struct Toyota: Car {}
let a: Car = Toyota()
but we can't generally (with notable exception of Swift's standard library collection types) do this:
struct Container<T> {}
let c: Container<Car> = Container<Toyota>() // ERROR
It is said that Container<Car> and Container<Toyota> are not covariant
Result has a mapError function that should make it fairly painless:
completion(response.result.mapError { $0 as Error } )

Swift Test: Controlling moc api client behaviour

I am writing test for my project and declared a remote API client protolcol:
public protocol ApiClient {
func load(completion: #escaping ([Any]?, String?))
}
and defined a moc api client that confirms to ApiClient:
class MocApiClient: ApiClient {
func loadFlights(completion: #escaping ([Any]?, String?)) {
// Load a sample JSON file and return it as response
}
}
this way I am able return a response by loading a JSON file. This is the happy path of the test. After it I started to think about testing different possible response types and decided that I should be able to alter behaviour of the MocApiClient and defined this:
enum TestPath {
case success
case failure
}
and using it with MocApiClient:
class MocApiClient: ApiClient {
var path: TestPath = .success
func load(completion: #escaping ([Any]?, String?) -> Void) {
switch path {
case .success:
completion([...], nil)
case .failure:
completion(nil, "error message")
}
}
}
Doyu think this is a good solution?
Do you have any beter approachs?
Your approach seems fine if you have just a few simple tests.
However, if you have a relatively complex logic and need to test many positive and negative paths, an alternative to your solution would be creating several different mocking objects, each one having a single purpose.
That way, you'll avoid a massive all-purpose mocking object problem and also will be able to define your mocking objects right inside the tests methods that use them, thus making your tests even more focused and independent.
Something like this:
func testSuccessfulResponse() {
class MockApiClientSuccessfulResponse: ApiClient {
...
}
...
}
func testMalformedResponse() {
class MockApiClientMalformedResponse: ApiClient {
...
}
...
}
func testInconsistentData() {
class MockApiClientInconsistentData: ApiClient {
...
}
...
}
Hope this helps and happy unit testing!

Dependency Injection for Static Functions for Unit Test in Swift

I know this looks like a common question but after reading 10-15 tutorial and looking how can I write test for my service class. I can't solve moving static functions to protocol or etc.. for dependency injection
I have a network layer like below image. All my function classes (like fetch users, news, media etc..) calls "Service Caller" class and after that If response is error; calls "Service Error" class to handle error and If not error, decode the JSON.
My problem is that I'm calling service class as a static function like "ServiceCaller.performRequest" and If It gets error I'm also calling error class as static like "ServiceError.handle". Also It calls URLCache class to get path of request url. I'm not sure how can I make them dependency inject and mock in test class. As I find in tutorials, I should write it like;
protocol MyProtocol{
func myfunction() -> Void
}
class A{
let testProtocol = MyProtocol!
init(pro: MyProtocol){
testProtocol = pro
}
}
and in setup function in test class it probably;
myMockProtocol = ...
myTestclass = A.init(pro: myMockProtocol)
but I can't find how can I get ride of static calls like ServiceCaller.performRequest or ServiceError.handle..; (Simplified version in the bottom part of question)
class AppInitService{
static func initAppRequest(_ completion: #escaping (_ appInitRecevingModel: Result<AppInitRecevingModel>) -> Void) {
let sendingModel = AppInitSendingModel(cmsVersion: AppDefaults.instance.getCMSVersion())
let route = ServiceRouter(method: .post, path: URLCache.instance.getServiceURL(key: URLKeys.initApp), parameters: (sendingModel.getJSONData()), timeoutSec: 1)
ServiceCaller.performRequest(route: route) { (result) in
if let error = result.error{
if let statusCode = result.response?.statusCode{
completion(.error(ServiceError.handle(error: error, statusCode: statusCode)))
}else{
completion(.error(ServiceError.handle(error: error, statusCode: error._code)))
}
}else{
if let data = result.data{
do{
var responseJson = JSON(data)
responseJson["idleTimeoutInMinutes"] = 10
let input = try AppInitRecevingModel(data: responseJson.rawData())
completion(.success(input))
}catch let error{
completion(.error(ServiceError.handle(error: error, statusCode: -1002)))
}
}
}}
}
}
My Test class:
class MyProjectAppInitTests: XCTestCase {
var appInitTest: AppInitService!
override func setUp() {
super.setUp()
// Put setup code here. This method is called before the invocation of each test method in the class.
appInitTest = AppInitService.init()
}
override func tearDown() {
// Put teardown code here. This method is called after the invocation of each test method in the class.
appInitTest = nil
super.tearDown()
}
func testExample() {
// This is an example of a functional test case.
// Use XCTAssert and related functions to verify your tests produce the correct results.
let testParamater = ["string":"test"]
let route = ServiceRouter(method: .post, path: "/testPath", parameters: testParamater.getJSONData(), timeoutSec: 10)
appInitTest. //cant call anything in here
}
Tutorials I looked for Unit Test;
https://www.raywenderlich.com/150073/ios-unit-testing-and-ui-testing-tutorial
https://www.swiftbysundell.com/posts/time-traveling-in-swift-unit-tests
https://marcosantadev.com/test-doubles-swift
http://merowing.info/2017/04/using-protocol-compositon-for-dependency-injection/
EDIT: One solution maybe writing init class for whole network layer and service classes then get rid of static functions? But I'm not sure If It will be a good approach.
EDIT 2: Simplified Code;
class A{
static func b(completion:...){
let paramater = ObjectModel(somevariable: SomeClass.Singleton.getVariable()) //Data that I sent on network request
let router = ServiceRouter(somevariable: SomeClassAgain.Singleton.getsomething()) //Router class which gets parameters, http method etc..
NetworkClass.performNetworkRequest(sender: object2){ (result) in
//Result - What I want to test (Write UnitTest about)
}
}
}
Use mocking.
class ServiceCallerMock: ServiceCaller {
override class func performRequest(route: ServiceRouter) -> (Any?) -> Void? {
//your implementation goes here
}
}
You could mock ServiceCaller and override the performRequest method, then change the function to:
static func initAppRequest(_ completion: #escaping (_ appInitRecevingModel: Result<AppInitRecevingModel>) -> Void, serviceCaller: ServiceCaller.Type = ServiceCaller.self) {
...
serviceCaller.performRequest(route: route) { (result) in
...
}
Then you could call the initAppRequest function using your mock implementation of ServiceCaller.

Swift Cast AnyObject to Block

So I am using the Salesforce SDK and built bridging headers for the entire SDK.
They provide a block syntax which hasn't translated into the most usable code. For instance,
func sendRESTRequest(request: SFRestRequest!, failBlock: SFRestFailBlock!, completeBlock: AnyObject!)
The complete block is AnyObject!. I was able to get around this with
var block : #objc_block (dataResponse :AnyObject!) -> Void = { dataResponse in //I handle the response}
restService.sendRESTRequest(request, failBlock: { (error :NSError!) -> Void in
}, completeBlock: unsafeBitCast(block, AnyObject.self))
So far this works fine. However, now I am trying to build unit testing for this code. I have created a mock class for SFRestAPI which is the class where the function "sendRESTRequest" resides. For testing purposes, I am trying to mock out the completeBlock: parameter by passing mock "data" that would be returned from the REST service.
class MockSFRestAPI : SFRestAPI {
override func sendRESTRequest(request: SFRestRequest!, failBlock: SFRestFailBlock!, completeBlock: AnyObject!) {
//Convert complete block into a closure and pass in some test data
}
}
The issue is, I am unable to cast AnyObject! to a block like I was able to cast the block to AnyObject like above.
Some of my attempts have been:
var block = completeBlock as #objc_block (AnyObject! -> Void)
var block2: (AnyObject! -> Void) = unsafeBitCast(completeBlock, (#objc_block (AnyObject! -> Void)))
There have been many more attempts, but these are the only two that seem relatively sane. So, is this possible in Swift? The issue seems to be that I cannot provide a closure "type" to the second parameter of the unsafeBitCast method. I want to turn it into a closure so I can call it in my mock method and pass in some fake data.
The best way to handle this situation is to create your own typealias for your block:
typealias MyFunBlock = #objc_block (dataResponse :AnyObject!) -> Void;
Then you can use that to unsafebitcast:
var block: MyFunBlock = unsafeBitCast(completeBlock, MyFunBlock.self) as MyFunBlock;

Resources