Unit testing methods containing AWSAPIGatewayResponse in swift - ios

I have the following method and I'm trying to unit test this in iOS/Swift
func apiResponseResults(response: AWSAPIGatewayResponse, sessionObject: Session) {
if response.statusCode == 200 {
// code
} else {
// code
}
}
This method is called from my api invoke method when the response is received. I want to unit test this by creating a fake AWSAPIGatewayResponse object.
I am not able to create AWSAPIGatewayResponse object. Is there a way to initialize this object in my unit testing class. I just wanna create an AWSAPIGatewayResponse object with statusCode as 200 and 300. Any help is appreciated.

Hi Pradeep, welcome to StackOverflow. 👋
You could define a protocol with the subset of functionality you are interested in from AWSAPIGatewayResponse, make AWSAPIGatewayResponse conform to it, and make apiResponseResults(response:, session:) expect a value of that type.
protocol Response {
var statusCode: Int { get }
}
extension AWSAPIGatewayResponse: Response { }
func apiResponseResults(response: Response, sessionObject: Session) { ... }
In your tests your can define a test double conforming to Response, and use that as the input parameter for your method.
struct TestResponse: Response {
let statusCode: Int
}
// Use it like this
let response = TestResponse(statusCode: 200)
Hope this helps.

Related

How to mock classes of external framework with delegates in iOS?

I am working in an iOS application called ConnectApp and I am using a framework called Connector. Now, Connector framework completes actual connection task with BLE devices and let my caller app (i.e. ConnectApp) know the connection request results through ConnectionDelegate. Let's see example code,
ConnectApp - host app
class ConnectionService: ConnectionDelegate {
func connect(){
var connector = Connector()
connector.setDelegate(self)
connector.connect()
}
func onConnected(result: ConnectionResult) {
//connection result
}
}
Connector Framework
public class ConnectionResult {
// many complicated custom variables
}
public protocol ConnectionDelegate {
func onConnected(result: ConnectionResult)
}
public class Connector {
var delegate: ConnectionDelegate?
func setDelegate(delegate: ConnectionDelegate) {
self.delegate = delegate
}
func connect() {
//…..
// result = prepared from framework
delegate?.onConnected(result)
}
}
Problem
Sometimes developers have no BLE device and we need to mock the Connector layer of framework. In case of simple classes (i.e. with simpler methods) we could have used inheritance and mock the Connector with a MockConnector which might override the lower tasks and return status from MockConnector class. But when I need to deal with a ConnectionDelegate which returns complicated object. How can I resolve this issue?
Note that framework does not provide interfaces of the classes rather we need to find way around for concrete objects like, Connector, ConnectionDelegate etc.
Update 1:
Trying to apply Skwiggs's answer so I created protocol like,
protocol ConnectorProtocol: Connector {
associatedType MockResult: ConnectionResult
}
And then injecting real/mock using strategy pattern like,
class ConnectionService: ConnectionDelegate {
var connector: ConnectorProtocol? // Getting compiler error
init(conn: ConnectorProtocol){
connector = conn
}
func connect(){
connector.setDelegate(self)
connector.connect()
}
func onConnected(result: ConnectionResult) {
//connection result
}
}
Now I am getting compiler error,
Protocol 'ConnectorProtocol' can only be used as a generic constraint because it has Self or associated type requirements
What am I doing wrong?
In Swift, the cleanest way to create a Seam (a separation that allows us to substitute different implementations) is to define a protocol. This requires changing the production code to talk to the protocol, instead of a hard-coded dependency like Connector().
First, create the protocol. Swift lets us attach new protocols to existing types.
protocol ConnectorProtocol {}
extension Connector: ConnectorProtocol {}
This defines a protocol, initially empty. And it says that Connector conforms to this protocol.
What belongs in the protocol? You can discover this by changing the type of var connector from the implicit Connector to an explicit ConnectorProtocol:
var connector: ConnectorProtocol = Connector()
Xcode will complain about unknown methods. Satisfy it by copying the signature of each method it needs into the protocol. Judging from your code sample, it may be:
protocol ConnectorProtocol {
func setDelegate(delegate: ConnectionDelegate)
func connect()
}
Because Connector already implements these methods, the protocol extension is satisfied.
Next, we need a way for the production code to use Connector, but for test code to substitute a different implementation of the protocol. Since ConnectionService creates a new instance when connect() is called, we can use a closure as a simple Factory Method. The production code can supply a default closure (creating a Connector) like with a closure property:
private let makeConnector: () -> ConnectorProtocol
Set its value by passing an argument to the initializer. The initializer can specify a default value, so that it makes a real Connector unless told otherwise:
init(makeConnector: (() -> ConnectorProtocol) = { Connector() }) {
self.makeConnector = makeConnector
super.init()
}
In connect(), call makeConnector() instead of Connector(). Since we don't have unit tests for this change, do a manual test to confirm we didn't break anything.
Now our Seam is in place, so we can begin writing tests. There are two types of tests to write:
Are we calling Connector correctly?
What happens when the delegate method is called?
Let's make a Mock Object to check the first part. It's important that we call setDelegate(delegate:) before calling connect(), so let's have the mock record all calls in an array. The array gives us a way to check the call order. Instead of having the test code examine the array of calls (acting as a Test Spy which just records stuff), your test will be cleaner if we make this a full-fledged Mock Object — meaning it will do its own verification.
final class MockConnector: ConnectorProtocol {
private enum Methods {
case setDelegate(ConnectionDelegate)
case connect
}
private var calls: [Methods] = []
func setDelegate(delegate: ConnectionDelegate) {
calls.append(.setDelegate(delegate))
}
func connect() {
calls.append(.connect)
}
func verifySetDelegateThenConnect(
expectedDelegate: ConnectionDelegate,
file: StaticString = #file,
line: UInt = #line
) {
if calls.count != 2 {
fail(file: file, line: line)
return
}
guard case let .setDelegate(delegate) = calls[0] else {
fail(file: file, line: line)
return
}
guard case .connect = calls[1] else {
fail(file: file, line: line)
return
}
if expectedDelegate !== delegate {
XCTFail(
"Expected setDelegate(delegate:) with \(expectedDelegate), but was \(delegate)",
file: file,
line: line
)
}
}
private func fail(file: StaticString, line: UInt) {
XCTFail("Expected setDelegate(delegate:) followed by connect(), but was \(calls)", file: file, line: line)
}
}
(That business with passing around file and line? This makes it so that any test failure will report the line that calls verifySetDelegateThenConnect(expectedDelegate:), instead of the line that calls XCTFail(_).)
Here's how you'd use this in ConnectionServiceTests:
func test_connect_shouldMakeConnectorSettingSelfAsDelegateThenConnecting() {
let mockConnector = MockConnector()
let service = ConnectionService(makeConnector: { mockConnector })
service.connect()
mockConnector.verifySetDelegateThenConnect(expectedDelegate: service)
}
That takes care of the first type of test. For the second type, there's no need to test that Connector calls the delegate. You know it does, and it's outside your control. Instead, write a test to call the delegate method directly. (You'll still want it to make a MockConnector to prevent any calls to the real Connector).
func test_onConnected_withCertainResult_shouldDoSomething() {
let service = ConnectionService(makeConnector: { MockConnector() })
let result = ConnectionResult(…) // Whatever you need
service.onConnected(result: result)
// Whatever you want to verify
}
You could try
protocol MockConnector: Connector {
associatedType MockResult: ConnectionResult
}
Then, for each connector you need to mock, define a concrete class that conforms to this mock connector
class SomeMockConnector: MockConnector {
struct MockResult: ConnectionResult {
// Any mocked variables for this connection result here
}
// implement any further requirements from the Connector class
var delegate: ConnectionDelegate?
func connect() {
// initialise your mock result with any specific data
let mockResult = MockResult()
delegate?.onConnected(mockResult)
}
}

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!

Swift - How to Network disable on Unit Test

Is there any way to close Internet Connection for Unit Test? I have to check async function when the device on online or not. How can I do that ?
I know the Additional tools Package but I want to write this feature with programmatically.
Thanks in Advance
This is how I mock an API call. The steps requires
1) No internet connection
2) Mocking the API Call and data
I have created data models for the api response and this is how I am mocking it
func buildActivityList() -> ActivityResponse {
let resp = ActivityResponse(json: .null)
let userHistory = ActivityUserHistory(json: .null)
userHistory.time = "2018-02-16T07:41:54.046Z"
resp.userHistory = [userHistory]
//Add all other relevant data
return resp
}
Next you need to override the function were you are calling API and pass the above mocked data as API response.
Suppose you have a class where you call the api
class UserService {
func getUserDetails(completion: (_: ActivityResponse?, _: Error?)) {
//get RESPONSE from server
let data = ActivityResponse(object: RESPONSE)
return completion(data, nil)
}
}
Now in your test class call the buildActivityList() which we created and pass it in the mock class.
class MockUserService: UserService {
override func getUserDetails(completion: (_: ActivityResponse?, _: Error?)) {
return completion(buildActivityList(), nil)
}
}
This way whenever you test an API call, the mock data will be injected. Thus no need for internet connection.
Hope this helps

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.

Mentally parse func declaration with generics and completion handler

public protocol ResponseJSONObjectSerializable {
init?(json: SwiftyJSON.JSON)
}
public struct Response<Value, Error: ErrorType> {
...
}
public func responseArray<T: ResponseJSONObjectSerializable>(completionHandler: Response<[T], NSError> -> Void) -> Self {
...
}
So far I understand the last function to mean that the type declaration requires generic type T which follows the protocol ResponseJSONObjectSerializable which is used in a completionHandler which takes a Response struct which has a type declaration of <Value, NSError> -> Void and then returns a self?
I feel like I might grok all of it except the last self part.
You're right about the first two declarations.
The last one is a bit odd because of how Alamofire does response serializers. You can chain multiple serializers like this:
Alamofire.request(myRequest)
.responseString { // handle response as string }
.responseArray { // handle response as array }
When that code gets called this is what happens:
Alamofire.request(myRequest) creates the request and a queue for handlers
.responseString & .responseArray get their response serializers added to the queue
The network call happens
When it's done (whether it fails or succeeds), the queue calls all of the response serializers that were added to it (i.e., .responseString & .responseArray)
When each serializer gets run, it's completion handler can be used to "return" results to the caller (which it can't do directly because it's async)
This code would does (almost) the same thing:
let manager = Alamofire.Manager.sharedInstance
manager.startsRequestImmediately = false
let alamofireRequest = manager.request(myRequest)
alamofireRequest.responseString { // handle response as string }
alamofireRequest.responseArray { // handle response as array }
alamofireRequest.resume()
But manager.startsRequestImmediately = false means that the network call doesn't get started until alamofireRequest.resume() is called. It's true by default so all of the response serializers have to be added as part of the same statement as manager.request(myRequest).
Since the response serializers return self, we can shorten this:
let alamofireRequest = manager.request(myRequest)
alamofireRequest.responseString { // handle response as string }
alamofireRequest.responseArray { // handle response as array }
To this:
let alamofireRequest = manager.request(myRequest)
.responseString { // handle response as string }
.responseArray { // handle response as array }
And if we use manager.startsRequestImmediately = true then we don't need the local var for the request at all (since we don't have to call alamofireRequest.resume()):
manager.request(myRequest)
.responseString { // handle response as string }
.responseArray { // handle response as array }

Resources