XCTKeyPathExpectation returns an unexpected error - ios

I've tried to use XCTKeyPathExpectation in an async environment and could not get it to work. The error I'm getting is confusing me a lot. Because the error stated is not really an error in my opinion...
I've created a very simple test to see if I did something wrong. Using the following two classes:
TestMock.swift:
import Foundation
#testable import UnitTests
final class TestMock: NSObject {
#objc private(set) var testCalled: Bool = false
func test() {
self.testCalled = true
}
}
UnitTestsTests.swift:
import XCTest
#testable import UnitTests
final class UnitTestsTests: XCTestCase {
var testMock: TestMock!
override func setUpWithError() throws {
self.testMock = TestMock()
// Put setup code here. This method is called before the invocation of each test method in the class.
}
override func tearDownWithError() throws {
self.testMock = nil
try super.tearDownWithError()
// Put teardown code here. This method is called after the invocation of each test method in the class.
}
func testExample() throws {
let expectation = XCTKeyPathExpectation(keyPath: \TestMock.testCalled,
observedObject: self.testMock,
expectedValue: true)
self.testMock.test()
self.wait(for: [expectation], timeout: 1.0)
}
}
It gives me the error:
testExample(): Asynchronous wait failed: Exceeded timeout of 1 seconds, with unfulfilled expectations: "Expect value of 'Swift.ReferenceWritableKeyPath<UnitTestsTests.TestMock, Swift.Bool>' of <UnitTestsTests.TestMock: 0x600003bfc090> to be 'true', was 'true'".
The only thing I can think of is that is comparing a Swift.Bool with an Objective-C Bool. But not sure how to fix this.

It appears that I needed to add the word 'dynamic' to the declaration of testCalled.
#objc dynamic private(set) var testCalled = false

Related

How to initialize WidgetInfo for unit testing in Xcode?

I am trying to initialize the WidgetKit struct WidgetInfo for unit testing a function that takes WidgetInfo as an argument:
let widgetInfo = WidgetInfo()
This gives me the error:
'WidgetInfo' cannot be constructed because it has no accessible initializers
I tried adding:
extension WidgetInfo {
public init() {}
}
and I can initialize - yay! But then I try to set one of its properties
widgetInfo.family = .systemSmall
and get the error: Cannot assign to property: 'family' is a 'let' constant
I tried another initializer with arguments:
extension WidgetInfo {
public init(family: WidgetFamily, kind: String) {
self.family = family
self.kind = kind
}
}
and I get the error: 'let' property 'family' may not be initialized directly; use "self.init(...)" or "self = ..." instead
I'm stuck - is there a way for me to initialize WidgetInfo? Or another way to test a function that takes WidgetInfo as an argument?
Figured it out. I created a WidgetInfo protocol with the information I needed, changed the function to take the protocol as the argument and extended WidgetInfo:
protocol WidgetInfoProtocol {
var widgetFamily: WidgetFamily { get }
var widgetKind: String { get }
}
extension WidgetInfo: WidgetInfoProtocol {
var widgetFamily: WidgetFamily {
return family
}
var widgetKind: String {
return kind
}
}
This allowed me to create a mock for use in unit testing:
struct MockWidgetInfo: WidgetInfoProtocol {
var widgetFamily: WidgetFamily
var widgetKind: String
}

Swift delegate as a function parameter

I created my protocol as below:
import Foundation
protocol ITcpCLient: class {
func OnMessageReceived(_ message: String);
}
The class using protocol as below:
import Foundation
class tcpConnection {
var tcpClientdelegate: ITcpCLient?
init(client: ITcpCLient) {
self.tcpClientdelegate? = client
if self.tcpClientdelegate == nil {
print("tcpClient Delegate is nil!")
}
}
func trigger() {
tcpClientdelegate?.OnMessageReceived("From Trigger")
}
}
My ViewController Class is below:
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
print("viewDidLoad!")
let myTcpConnection = tcpConnection(client: self)
myTcpConnection.trigger()
}
}
extension ViewController: ITcpCLient {
func OnMessageReceived(_ message: String) {
print("onMessageReceived")
print(message)
}
}
The output is: tcpClient Delegate is nil!
If I create delegate without question mark, the code working as expected.But when I use optional type, I cant assign viewcontroller class as a delegate.
Since self.tcpClientdelegate is nil then appending ? operator will cause whole statement not to be run => which causes that delegate to be un-assinged = nil
self.tcpClientdelegate? = client
so replace with
self.tcpClientdelegate = client
The problem lies in the init:
self.tcpClientdelegate? = client
The postfix ? operator will not carry out whatever operation you specified if its operand is nil.
Here, self.tcpClientdelegate is nil, so the value is not assigned.
From the Swift Language Reference:
Optional-chaining expressions must appear within a postfix expression,
and they cause the postfix expression to be evaluated in a special
way. If the value of the optional-chaining expression is nil, all of
the other operations in the postfix expression are ignored and the
entire postfix expression evaluates to nil.
Just assign it normally to fix the problem, because you don't care about whether self.tcpClientdelegate was nil or not:
self.tcpClientdelegate = client

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 - Instance member cannot be used on type custom class [duplicate]

This question already has answers here:
How to initialize properties that depend on each other
(4 answers)
Closed 6 years ago.
I have no idea why I cannot call token on this argument, I keep getting error on this line
.ExtraHeaders(["Authorization": token])
The error
Instance member 'token' cannot be used on type 'SocketIOManager'
Full code
import SocketIOClientSwift
import KeychainAccess
class SocketIOManager: NSObject {
static let sharedInstance = SocketIOManager()
let keychain = Keychain(server: "https://testing.herokuapp.com", protocolType: .HTTPS)
var token: String {
get {
return String(keychain["token"])
}
}
let socket = SocketIOClient(socketURL: NSURL(string:
"https://testing.herokuapp.com")!,
options: [.Log(true), .ExtraHeaders(["Authorization": token]) ])
override init() {
super.init()
}
func establishConnection() {
socket.connect()
}
func closeConnection() {
socket.disconnect()
}
}
The problem can be distilled into a tiny bit of code:
class Test {
let test = "test"
let test2 = test + "2" // instance member 'test' cannot be used on type 'Test'
}
The problem is that when those fields are initialized, the object doesn't exist yet. There are two ways of dealing with this:
class Test {
let test = "test"
lazy var test2:String = self.test + "2" // since test2 is lazy, it only gets computed at a time when the object already exists
}
and
class Test {
let test = "test"
let test2:String
init() {
test2 = test + "2" // you can set constants in initializers
}
}
The downside of the first option is that you have to use var, and you need to explicitly write self, the downside of the second is that your initializing code is not right where the declaration is. (though init() is a very logical place to look next) Both way require you to explicitly state the type. You have to decide what's more important to you, I'd opt for leaving constants constants when possible. But when you're using var already, might as well for the first option.
Note that if you choose the second option and your class inherits from another class, you need to initialize the member before calling super.init():
class Test: SuperTest {
let test = "test"
let test2:String
override init() {
test2 = test + "2"
super.init()
}
}
The compiler will yell at you if you don't do this, so it's hard to get it wrong.

XCTest expect method call with swift

How to write a test that expects a method call using swift and XCTest?
I could use OCMock, but they don't officially support swift, so not much of an option.
As you said OCMock does not support Swift(nor OCMockito), so for now the only way I see is to create a hand rolled mock. In swift this is a little bit less painful since you can create inner classes within a method, but still is not as handy as a mocking framework.
Here you have an example. The code is self explanatory, the only thing I had to do(see EDIT 1 below) to make it work is to declare the classes and methods I want to use from the test as public(seems that the test classes do not belong to the same application's code module - will try to find a solution to this).
EDIT 1 2016/4/27:
Declaring the classes you want test as public is not necessary anymore since you can use the "#testable import ModuleName" feature.
The test:
import XCTest
import SwiftMockingPoC
class MyClassTests: XCTestCase {
func test__myMethod() {
// prepare
class MyServiceMock : MyService {
var doSomethingWasCalled = false
override func doSomething(){
doSomethingWasCalled = true
}
}
let myServiceMock = MyServiceMock()
let sut = MyClass(myService: myServiceMock)
// test
sut.myMethod()
// verify
XCTAssertTrue(myServiceMock.doSomethingWasCalled)
}
}
MyClass.swift
public class MyClass {
let myService: MyService
public init(myService: MyService) {
self.myService = myService
}
public func myMethod() {
myService.doSomething()
}
}
MyService.swift
public class MyService {
public init() {
}
public func doSomething() {
}
}

Resources