Mock a function inside another function return value - swift

I have an SDK framework and I want to test a function that contains a call to another function(inside a different class), so I create a protocol for this class:
#protocol ManagerProtocol <NSObject>
-(BOOL)checkForStatus:(NSArray <NSString*>*)array;
#end
#interface Manager : NSObject <ManagerProtocol>
//some other code....
#end
#implementation Manager
-(BOOL)checkForStatus:(NSArray <NSString*>*)array {
//do some logic, will return false for now
return NO;
}
And this is the call for this method:
-(void)start {
Manager * m = [[Manager alloc] init];
NSArray * arr = #["foo",#"bar"];
if (![m checkForStatus:[arr]) {
//do something
return;
}
//continue the process
}
Now I want to create a unit test for the start method and I want that checkForStatus will always return a True value, so I used Mockingbird framework, first I created a mock protocol:
class ManagerMock: ManagerProtocol {
override func check(forStatus array: [String]) -> Bool {
return true
}
}
And in my test I mock with Mockingbird and call the start function:
let m = mock(ManagerMock.self)
given(m.check(checkForStatus: any())).willReturn(true)
start()
But the problem is that checkForStatus inside start will return false instead of true, Did I miss something?

Related

Use swift class returned by an objective c class in swift

I have a cocoa pod library with a legacy objective c service that is partially refactored into swift. The objective c service uses and returns some swift classes.
The cocoa pod library is used in a project with both objective c and swift.
When the service is used from swift code, the returned class types are no longer recognized as its original types, but rather as '__ObjC.' class types.
Any idea what should be done or how to tackle this problem?
Simplified code below:
------ cocoa pod library ------
The swift classes
#objc public class PlayableItem: NSObject{
#objc public var id: Int {
get {
return 0;
}
}
#objc public var title: String {
get {
return "";
}
}
}
#objc public class ApiTrack : PlayableItem {
private var _id: Int
#objc override public var id: Int {
get {
return self._id
}
set{
self._id = newValue
}
}
private var _title: String
#objc override public var title: String {
get {
return self._title
}
set{
self._title = newValue
}
}
}
#objc public class ApiMessage: ApiTrack {
//extra properties etc
}
#objc public class ApiSong: ApiTrack {
//extra properties etc
}
The objective c legacy service (simplified test version)
TestProxy.h file
#import <Foundation/Foundation.h>
#import <TunifyApi/TunifyApi-Swift.h>
#interface TestProxy : NSObject
+ (TestProxy *) sharedProxy;
#property (nonatomic, readonly) PlayableItem *firstPlayableItem;
#end
TestProxy.m file
#import "TestProxy.h"
static TestProxy *shared = nil;
#implementation TestProxy
+ (TestProxy *) sharedProxy{
#synchronized(shared){
if (!shared || shared == nil){
shared = [[TestProxy alloc] init];
}
}
return shared;
}
- (PlayableItem *) firstPlayableItem{
//Returns a playable item that can be a ApiMessage or ApiSong
}
#end
I also have a general header file ('TunifyApi.h') that imports all the objective c headers that we need to use:
#import "TestProxy.h"
------ files from project ------
The bridging header imports the general header file of the pod:
#import "TunifyApi/TunifyApi.h"
Inside a swift file, when trying to use the TestProxy service:
private func testFirstTrack(){
//this works, but no properties can be accesssed
var firstPlayableItem1 = TestProxy.shared().firstPlayableItem
//this results in an compile error
var firstPlayableItem2 : PlayableItem? = TestProxy.shared().firstPlayableItem
}
The compile error:
Cannot convert value of type '__ObjC.PlayableItem?' to specified type 'TunifyApi.PlayableItem?'
When using the code from objective c, everything looks good:
- (void) testFirstTrack{
PlayableItem *firstPlayableItem = [TestProxy sharedProxy].firstPlayableItem;
//All properties can be accessed, downcasting is available etc
if (firstPlayableItem && [firstPlayableItem isKindOfClass:[ApiTrack class]]){
ApiTrack *firstTrack = (ApiTrack *) firstPlayableItem;
}
}
The reason for this error was because I used the swift bridging file of the library in some of the header files of the project.
After moving the imports to the .m files everything worked as expected. So this needs to be in the .m files:
#import <TunifyApi/TunifyApi-Swift.h>
If the classes are needed in any objective-c header file, the correct way to use them is to forward declare them like this:
#class PlayableItem;

Test String extension in Objective c

I have this in my project -
extension String {
func doSomething() -> String {
return "doSomething"
}
}
I'm trying to write an Objective C test for it. (This is an SDK that can be used by Objective C projects) but when calling -
NSString* actualResult = [string doSomething];
I'm getting this error -
No visible #interface for 'NSString' declares the selector 'doSomething'
How can it be done?
I've tried on NSString but the same error appears, #objc can only be
added to classes not structs
Only adding #objc won't be sufficient, in order to access swift function inside your Objective-C file you have to import <ProductModuleName>-Swift.h in your .m file
For example
#import <Foundation/Foundation.h>
#import "StackOverflow-Swift.h"
#interface SampleClass : NSObject
#end
#implementation SampleClass
- (void)test {
NSString *testing = #"abcd";
[testing doSomething];
}
#end
Finally
public extension NSString {
#objc func doSomething() -> String {
return "doSomething"
}
}

Expose an interface of a class loaded from a framework at runtime

I want to load and manipulate SKUIImageColorAnalyzer and
SKUIAnalyzedImageColors objects from the private StoreKitUI.framework.
First, I attempt to load the framework at runtime:
guard case let libHandle = dlopen("/System/Library/PrivateFrameworks/StoreKitUI.framework/StoreKitUI", RTLD_NOW) where libHandle != nil else {
fatalError("StoreKitUI not found")
}
Then, I verify that the SKUIImageColorAnalyzer class can be found:
guard let analyzerClass: AnyClass = NSClassFromString("SKUIImageColorAnalyzer") else {
fatalError("SKUIImageColorAnalyzer lookup failed")
}
I want to use the analyzeImage: class method on SKUIImageColorAnalyzer, which takes in a UIImage for analysis and returns an SKUIAnalyzedImageColors object. I do this by verifying the analyzeImage: selector exists on the SKUIImageColorAnalyzer object, and recreate the function:
let selector: Selector = "analyzeImage:"
guard case let method = class_getClassMethod(analyzerClass, selector) where method != nil else {
fatalError("failed to look up \(selector)")
}
// recreate the method's implementation function
typealias Prototype = #convention(c) (AnyClass, Selector, UIImage) -> AnyObject? // returns an SKUIAnalyzedImageColors object
let opaqueIMP = method_getImplementation(method)
let function = unsafeBitCast(opaqueIMP, Prototype.self)
Now, I can get a UIImage object and pass that in as the argument to the function:
let img = UIImage(named: "someImage.jpg")!
let analyzedImageColors = function(analyzerClass, selector, img) // <SKUIAnalyzedImageColors: 0x7f90d3408eb0>
I know that analyzedImageColors is of type SKUIAnalyzedImageColors, but the compiler still thinks its type is AnyObject based on the way I declared Prototype above. Now I want to access the properties of an SKUIAnalyzedImageColors object.
From the header, I can see that there are properties such as backgroundColor, textPrimaryColor, and textSecondaryColor on the object. I can access these properties using valueForKey, but I'd like to expose a public interface on SKUIAnalyzedImageColors so I can access these properties.
My first attempt was something like this:
// Create a "forward declaration" of the class
class SKUIAnalyzedImageColors: NSObject { }
// Create convenience extensions for accessing properties
extension SKUIAnalyzedImageColors {
func backgroundColor() -> UIColor {
return self.valueForKey("_backgroundColor") as! UIColor
}
func textPrimaryColor() -> UIColor {
return self.valueForKey("_textPrimaryColor") as! UIColor
}
func textSecondaryColor() -> UIColor {
return self.valueForKey("_textSecondaryColor") as! UIColor
}
}
// ...
// modify the prototype to return an SKUIAnalyzedImageColors object
typealias Prototype = #convention(c) (AnyClass, Selector, UIImage) -> SKUIAnalyzedImageColors?
// ...
// access the properties from the class extension
analyzedImageColors?.backgroundColor() // Optional(UIDeviceRGBColorSpace 0.262745 0.231373 0.337255 1)
This still requires me to use valueForKey. Is there a way to expose a public interface on a class from a framework loaded at runtime?
The easiest way to do dynamic Objective-C stuff is to use Objective-C.
ImageAnalyzer.h:
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
#interface SKUIAnalyzedImageColors : NSObject
#property (nonatomic, readonly) UIColor* backgroundColor;
#property (nonatomic, readonly) BOOL isBackgroundLight;
#property (nonatomic, readonly) UIColor* textPrimaryColor;
#property (nonatomic, readonly) UIColor* textSecondaryColor;
#end
SKUIAnalyzedImageColors* _Nullable analyzeImage(UIImage* image);
NS_ASSUME_NONNULL_END
ImageAnalyzer.m:
#import "ImageColorAnalyzer.h"
#include <dlfcn.h>
static Class _SKUIImageColorAnalyzerClass;
#interface SKUIImageColorAnalyzer : NSObject
+ (SKUIAnalyzedImageColors*)analyzeImage:(UIImage*)arg1;
#end
SKUIAnalyzedImageColors* analyzeImage(UIImage* image)
{
if (!_SKUIImageColorAnalyzerClass)
{
if (!dlopen("/System/Library/PrivateFrameworks/StoreKitUI.framework/StoreKitUI", RTLD_NOW))
{
NSLog(#"No framework.");
return nil;
}
_SKUIImageColorAnalyzerClass = NSClassFromString(#"SKUIImageColorAnalyzer");
if (!_SKUIImageColorAnalyzerClass)
{
NSLog(#"No Class.");
return nil;
}
}
return [_SKUIImageColorAnalyzerClass analyzeImage:image];
}
You can then use the analyzeImage function and the SKUIAnalyzedImageColors class easily from either Swift or Objective-C code.
if let image = UIImage(named:"MyImage") {
if let colors = analyzeImage(image) {
print("Background Color: \(colors.backgroundColor)")
}
}
If you really want to do it all in Swift, first declare the parts of the SKUIAnalyzedImageColors Objective-C interface you want to use:
#objc protocol ImageColors {
var backgroundColor: UIColor { get }
var isBackgroundLight: Bool { get }
var textPrimaryColor: UIColor { get }
var textSecondaryColor: UIColor { get }
}
Then use unsafeBitCast to cast the opaque object instance to your desired Objective-C interface:
let img = UIImage(named: "someImage.jpg")!
let rawAnalyzedImageColors = function(analyzerClass, selector, img)
let analyzedImageColors = unsafeBitCast(rawAnalyzedImageColors, ImageColors.self)
print("Background color: \(analyzedImageColors.backgroundColor)")

polymorphic methods that return different class types in Swift?

I have a collection of heterogeneous object types (common superclass). Per element, I want to fetch a class type to instantiate. In ObjectiveC, I did something like this:
#implementation CommonClass
- (Class) secondaryAnnotationClass {
return [MKAnnotationView class]; // abstract implementation, just return default class
}
#end
#implementation SubclassFoo
- (Class) secondaryAnnotationClass {
return [FooAnnotationView class]; // my specific annotation class
}
#end
#implementation SubclassBar
- (Class) secondaryAnnotationClass {
return [BarAnnotationView class]; // my specific annotation class
}
#end
So what do I do to recreate this in Swift? I think it's something like the following, but I haven't yet done the right thing to make the compiler take the easy red dots away.
class CommonClass {
var secondaryAnnotationClass:Type {
return MKAnnotationView.self // abstract implementation, just return default class
}
}
class SubclassFoo:CommonClass {
var secondaryAnnotationClass:Type {
return FooAnnotationView.self // my specific annotation class
}
}
class SubclassBar:CommonClass {
var secondaryAnnotationClass:Type {
return BarAnnotationView.self // my specific annotation class
}
}
It seems that to keep Swift's type system happy, what I really need to say is that I'll return not just a Type (is that really the replacement for Class?), but that it will be MKAnnotationView or one of its subclasses.
You could have secondaryAnnotationClass return MKAnnotationView.Type:
class CommonClass {
var secondaryAnnotationClass: MKAnnotationView.Type {
return MKAnnotationView.self
}
}
class SubclassFoo:CommonClass {
override var secondaryAnnotationClass: MKAnnotationView.Type {
return FooAnnotationView.self
}
}
class SubclassBar:CommonClass {
override var secondaryAnnotationClass: MKAnnotationView.Type {
return BarAnnotationView.self
}
}
With this approach, if you have methods or properties specific to FooAnnotationView or BarAnnotationView that you need to use, you'll have to downcast. For example:
class FooAnnotationView: MKAnnotationView {
func myFunc() {
print("Foo")
}
}
let subclassFoo = SubclassFoo()
let annotation = subclassFoo.secondaryAnnotationClass() as! FooAnnotationView
annotation.myFunc() // Prints: "Foo"

Cast Swift / Objective C

I'm trying to translate a code from Objective-C to swift. Every now and then I run into problems with castings.
Here is an example:
There's a class deriving from NSObject which is supposed to simulate a UITableViewDataSource:
#import <Foundation/Foundation.h>
#interface ReloadDataWatcher : NSObject
- (void)reloadData;
- (BOOL)didReceiveReloadData;
#end
Here the class is used getting casted to UITableView
- (void)testQuestionListCausesTableReloadOnAvatarNotification {
ReloadDataWatcher *fakeTableView = [[ReloadDataWatcher alloc] init];
dataSource.tableView = (UITableView *)fakeTableView;
[dataSource avatarStoreDidUpdateContent: nil];
XCTAssertTrue([fakeTableView didReceiveReloadData], #"...");
}
Everything fine under Objective - C
Doing the same in Swift:
Here the class:
class ReloadDataWatcher: NSObject {
var didReloadData: Bool = false
func reloadData() {
... }
}
Leads to a "ReloadDataWatcher" is not convertible to UITableView Compiler Error.
func testQuestionListCausesTableReloadOnAvatarNotification() {
let fakeTableView : ReloadDataWatcher = ReloadDataWatcher()
dataSource.tableView = fakeTableView as UITableView
...
}
Why is that so ? Can I no longer cast from sub-class to a another subclass with the same parent (NSObject) ?
What would a solution be in that case?