I am new to using OCMock. I have the following simplified code I want to test -
#implementation ViewController
- (void)presentSomething:(NSString *)string {
NSLog(#"doSomethingElseWithString - %#", string);
}
- (BOOL)handleResult:(NSString *)result
{
BOOL handled = YES;
NSString *changedString = [result uppercaseString];
[self presentSomething:changedString];
return handled;
}
#end
and I have a test as follows
- (void)testExample {
ViewController *vc = [ViewController new];
id mockViewController = OCMPartialMock(vc);
OCMStub([mockViewController presentSomething:#"1234"]);
BOOL handled = [vc handleResult:#"1234"];
XCTAssertTrue(handled);
OCMVerify([mockViewController presentSomething:#"1234"]);
}
I want to verify presentSomething gets called with the correct argument after I call handleResult.
The test either gives a EXC_BAD_ACCESS while verifying or fails saying the presentSomething method was not invoked.
If I change the handleResult method as follows the test runs and passes.
- (BOOL)handleResult:(NSString *)result
{
BOOL handled = YES;
[self presentSomething:result];
return handled;
}
It seems that intermediate [result uppercaseString] and variable caused the issue. Is this an issue with how I am using OCMock or a bug in OCMock?
Must be trying to do a pointer comparison instead of isEquals. I haven't tried debugging the issue against the source code yet, but there is a workaround:
- (void)testExample
{
ViewController *vc = [ViewController new];
id mockViewController = OCMPartialMock(vc);
OCMExpect([mockViewController presentSomething:#"1234"]);
BOOL handled = [vc handleResult:#"1234"];
XCTAssertTrue(handled);
OCMVerifyAll(mockViewController);
}
Related
My app currently uses this deprecated function:
id unarchivedObject=[NSKeyedUnarchiver unarchiveObjectWithData:codedData];
if([unarchivedObject isKindOfClass:[NSDictionary class]]){
// currently returns TRUE when reading existing user data.
}
To update, I've converted to this:
id unarchivedObject=[NSKeyedUnarchiver unarchivedObjectOfClass:[NSDictionary class] fromData:codedData error:nil];
if([unarchivedObject isKindOfClass:[NSDictionary class]]){
// currently returns FALSE when reading existing user data.
}
The data was originally encoded like this:
-(void)encodeWithCoder:(NSCoder*)encoder{
[encoder encodeObject:text forKey:#"text"];
}
-(instancetype)initWithCoder:(NSCoder*)decoder{
if(self=[super init]){
text=[decoder decodeObjectForKey:#"text"];
}
What could be causing the IF statement to return FALSE using the newer code?
Please note that I am concerned primarily with reading existing data stored prior to deprecating the Archiving functions. Simply changing to the newer functions does not resolve the issue.
Interesting question! I've been supporting iOS 10.0 so I haven't encountered such issue until I saw this. I was tinkering for an hour and I successfully found the issue.
What could be causing the IF statement to return FALSE using the newer
code?
It's because your unarchivedObject object is nil!
If you use the parameter error in the new method, you would see an error like this:
Error Domain=NSCocoaErrorDomain Code=4864 "This decoder will only
decode classes that adopt NSSecureCoding. Class 'QTPerson' does not
adopt it." UserInfo={NSDebugDescription=This decoder will only decode
classes that adopt NSSecureCoding. Class 'QTPerson' does not adopt it.
But how do we get the correct value for this unarchivedObject and not nil? It would take a couple of steps.
First off, make your model/class conform to <NSCoding, NSSecureCoding>
Example:
QTPerson.h
#import <Foundation/Foundation.h>
#class QTPerson;
NS_ASSUME_NONNULL_BEGIN
#pragma mark - Object interfaces
#interface QTPerson : NSObject <NSCoding, NSSecureCoding>
#property (nonatomic, copy) NSString *text;
#end
NS_ASSUME_NONNULL_END
And then implement the protocol methods:
QTPerson.m
#import "QTPerson.h"
#implementation QTPerson
+ (BOOL)supportsSecureCoding {
return YES;
}
- (void)encodeWithCoder:(NSCoder *)coder {
[coder encodeObject:_text forKey:#"text"];
}
- (instancetype)initWithCoder:(NSCoder *)coder {
self = [super init];
if (self) {
_text = [coder decodeObjectOfClass:[NSString class] forKey:#"text"];
}
return self;
}
#end
And then when archiving an object, you would want to pass YES to the parameter requiringSecureCoding, like so:
QTPerson *person = [[QTPerson alloc] init];
person.text = #"Glenn";
NSData *codedData1 = [NSKeyedArchiver archivedDataWithRootObject:person requiringSecureCoding:YES error:nil];
[[NSUserDefaults standardUserDefaults] setValue:codedData1 forKey:#"boom"];
Lastly, when unarchiving, just do what you did correctly, like so:
NSData *codedData = [[NSUserDefaults standardUserDefaults] dataForKey:#"boom"];
NSError *er;
id unarchivedObject=[NSKeyedUnarchiver unarchivedObjectOfClass:[QTPerson class] fromData:codedData error:&er];
if([unarchivedObject isKindOfClass:[QTPerson class]]){
NSLog(#"TRUE!");
} else {
NSLog(#"FALSE!");
}
Voila! You'll get nonnull object unarchivedObject, hence the TRUE/YES value you're looking for!
I have an object like this:
typedef void (^ Completion) (Response *);
// Response class
#interface Response : NSObject {
NSDictionary * kdata;
}
- (id)initWithJson:(NSDictionary *)data;
#property (nonatomic, assign) NSDictionary * data;
#end
#implementation Response
- (id)initWithJson:(NSDictionary *)data { kdata = data; }
- (NSDictionary *) data { return kdata; }
- (void) setData: (NSDictionary *)data { kdata = data; }
- (NSDictionary *) msg { return kdata[#"msg"]; }
#end
// inside a networking class X implementation
- (void) doSomething:(completionBlock)completion {
NSDictionary * json = // get from networking function, which will always have key "msg".
Response * responseObj = [[Response alloc] initWithJson:json];
dispatch_async(dispatch_get_main_queue(), ^{
if (completion != nil) { completion (responseObj); }
});
}
// inside caller method
[X doSomething:^(Response * response) {
NSLog (#"%#", [response msg]);
}
This code will raise error on accessing kdata[#"msg"], even though I'm sure from the debug that the object was initialised properly with a dictionary contains key "msg". When I debug the object, on the watch window, it shows me that the kdata data type keeps changing, from NSArrayM, NSSet, NSDictionary, etc. And its contents also keep changing. I even add retain keyword when calling completion ([responseObj retain]); but still produce error.
But if the code in class X is changed into like this:
// inside a networking class X implementation
- (void) doSomething:(completionBlock)completion {
NSDictionary * json = // get from networking function, which will always have key "msg".
Response * responseObj = [[Response alloc] initWithJson:json];
if (completion != nil) { completion (responseObj); } // here is the change, no more switching to main thread
}
// inside caller method - no change here
[X doSomething:^(Response * response) {
NSLog (#"%#", [response msg]);
}
The code works perfectly. Why is that happened? This is built in Xcode without ARC.
EDIT: someone mentioned about the init. This is my mistake that what was written above is not exactly my code, and I copy the init method wrong. This is my init method:
- (instancetype) initWithData:(NSDictionary *)freshData {
NSParameterAssert(freshData); // make sure not nil
self = [super init];
if (self) {
kdata = freshData;
}
return self;
}
The problem is the object get's released right when you call the 'async' .
The way you declared your object is added to the autorelease pool since the control does not wait for 'async' to complete and the control return's by reaching the end of function 'doSomething' and releasing it's local objects which were added to the autorelease pool, and after that the memory location is used for other data and that's what you see confusing data.
I think by adding the __block specifier in front of your declaration you instruct the code to capture this object in following blocks strongly and release it when the block finished executing. Give it a try.
// inside a networking class X implementation
- (void) doSomething:(completionBlock)completion {
NSDictionary * json = // get from networking function, which will always have key "msg".
__block Response * responseObj = [[Response alloc] initWithJson:json];
dispatch_async(dispatch_get_main_queue(), ^{
if (completion != nil) { completion (responseObj); }
});
}
- (id)initWithJson:(NSDictionary *)data { kdata = data; }
You need call supers init here and return self.
Start to learn basics.
I am starting in Unit testing with objective-c and I need to know how to test blocks with OCMockito and Xcode 6.
I am testing an Interactor, this interactor should return an array as a block argument and I has to ask the Provider file for the elements.
This is the method I want to test:
- (void)userPoiListsWithSuccessBlock:(MNBSavePoisInteractorSuccess)success {
self.poiListEntityArray = [self.poiListProvider poiListsForUser:self.loggedUser];
self.poiListViewObjectArray = [self viewPoiListObjectListWithPoiLists:self.poiListEntityArray];
success(self.poiListViewObjectArray);
}
First, I setup the elements that I am going to use
self.mockPoiListProvider = mock([PoiListProvider class]);
self.sut = [[MNBSavePoisInteractor alloc] initWithManagedObjectContext:self.coreDataStack.managedObjectContext andPoiListProvider:self.mockPoiListProvider];
- (UserEntity *)loggedUserMock {
UserEntity *mockLoggedUser = [NSEntityDescription insertNewObjectForEntityForName:NSStringFromClass([UserEntity class]) inManagedObjectContext:self.coreDataStack.managedObjectContext];
mockLoggedUser.userId=#"1";
mockLoggedUser.username=#"user";
mockLoggedUser.loggedUser=#YES;
return mockLoggedUser;
}
- (InMemoryCoreDataStack *)coreDataStack{
if (!_coreDataStack) {
_coreDataStack = [[InMemoryCoreDataStack alloc] init];
}
return _coreDataStack;
}
- (PoiListEntity *)poiListFake {
PoiListEntity *fake = [NSEntityDescription insertNewObjectForEntityForName:#"PoiListEntity" inManagedObjectContext:self.coreDataStack.managedObjectContext];
fake.name = #"Test";
fake.poisCount = #2;
[fake addContributorsObject:[self loggedUserMock]];
return fake;
}
Then, I do the test. I am using Xcode 6 waitForExpectation to manage the asynchronous methods. I think I am doing something wrong.
- (void)waitForExpectation {
[self waitForExpectationsWithTimeout:5.0 handler:^(NSError *error) {
if (error) {
NSLog(#"Timeout Error: %#", error);
}
}];
}
- (void)testShouldReturnPoiLists {
XCTestExpectation *expectation = [self expectationWithDescription:#"Waiting method ends"];
[given([self.mockPoiListProvider poiListsForUser:[self loggedUserMock]]) willReturn:#[[self poiListFake]]];
[self.sut userPoiListsWithSuccessBlock:^(NSArray *results) {
[expectation fulfill];
XCTAssert(resutls.count == 1, #"Results %zd", resutls.count);
}];
[self waitForExpectation];
}
I understood if I give the object in willReturn in the given method, when I call the sut method that I want to test it should return what I give before. Is that true?
Thank you
I see no asynchronous code. You just want a block that captures the results, so use a __block variable to make the results available outside of the block. Then you can assert whatever you want:
- (void)testShouldReturnPoiLists {
[given([self.mockPoiListProvider poiListsForUser:[self loggedUserMock]]) willReturn:#[[self poiListFake]]];
__block NSArray *capturedResults;
[self.sut userPoiListsWithSuccessBlock:^(NSArray *results) {
capturedResults = results;
}];
assertThat(capturedResults, hasLengthOf(1));
}
The relationship between the length of 1 and the fake is hard to tell. Let's also parameterize the faking code:
- (PoiListEntity *)poiListFakeWithName:(NSString *)name count:(NSNumber *)count {
PoiListEntity *fake = [NSEntityDescription insertNewObjectForEntityForName:#"PoiListEntity" inManagedObjectContext:self.coreDataStack.managedObjectContext];
fake.name = name;
fake.poisCount = count;
[fake addContributorsObject:[self loggedUserMock]];
return fake;
}
With that, we can write more interesting tests.
I do want to add that it's important to "listen to the tests." There's a lot of convoluted set-up to dance around Core Data. That tells me that if you can rewrite things to be independent of Core Data — completely ignorant of it — everything will be much simpler.
I am trying to make an FoodCourt Application. I have 3 MutableArray in AppDelegate.h
// This are declaration and Getter and Setter methods for array.
AppDelegate.h
#property(strong,nonatomic)NSMutableArray* NameArray;
#property(strong,nonatomic)NSMutableArray* QtyArray;
#property(strong,nonatomic)NSMutableArray* KGArray;
-(NSMutableArray*)GetNameNSO;
-(NSMutableArray*)GetQtyNSO;
-(NSMutableArray*)GetKGNSO;
-(void)setNameArray:(NSMutableArray*)NameArray;
-(void)setQtyArray:(NSMutableArray*)QtyArray;
-(void)setKGArray:(NSMutableArray*)KGArray;
I have Implemented Getter and Setter methods in AppDelegate.m
Appdelegate.m
#synthesize NameArray =_NameArray;
#synthesize KGArray =_KGArray;
#synthesize QtyArray=_QtyArray;
-(NSMutableArray*)GetNameNSO
{
if(_NameArray == nil)
{
_NameArray = [[NSMutableArray alloc]init];
NSLog(#"%# GetName FROM HELPER",_NameArray);
return _NameArray;
}
else
{
return _NameArray;
}
}
-(NSMutableArray*)GetQtyNSO
{
if(_QtyArray == nil)
{
_QtyArray = [[NSMutableArray alloc]init];
NSLog(#"%# GetQTY FROM HELPER",_QtyArray);
return _QtyArray;
}
else
{
return _QtyArray;
}
}
-(NSMutableArray*)GetKGNSO
{
if(_KGArray == nil)
{
_KGArray = [[NSMutableArray alloc]init];
NSLog(#"%# GetKG FROM HELPER",_KGArray);
return _KGArray;
}
else
{
return _KGArray;
}
}
-(void)setQtyArray:(NSMutableArray *)QtyArray{
_QtyArray=QtyArray;
NSLog(#"%# SetQty FROM HELPER",_QtyArray);
}
-(void)setNameArray:(NSMutableArray *)NameArray
{
_NameArray =NameArray;
NSLog(#"%# SetName FROM HELPER",_NameArray);
}
-(void)setKGArray:(NSMutableArray *)KGArray
{
_KGArray =KGArray;
NSLog(#"%# SetKG FROM HELPER",_KGArray);
}
Setter Methods works fine as is it should. I am setting my values from FoodController.h in this setter methods and it give me Perfect log with all the values in method.
FoodController.h
AppDelegate *AppAdd;
AppAdd = [[AppDelegate alloc]init];
[AppAdd setNameArray:NameSegue];
[AppAdd setKGArray:KGQtySegue];
[AppAdd setQtyArray:QtySegue];
Above Code is called in prepareForSegue Method. after changing my application view i am trying to fetch this data from Getter method in OrderDetails.m But it give null or no value. I just don't understand why this methods do not work properly. Here's the code of OrderDetails.m file.
OrderDetails.m
AppDelegate *sc;
sc=[[AppDelegate alloc]init];
[DataName addObjectsFromArray:sc.GetNameNSO];
[DataQty addObjectsFromArray:sc.GetQtyNSO];
[DataQty addObjectsFromArray:sc.GetKGNSO];
I just don't understand Why this code is not working, Please Help me out! Thank you.
New Code For Getting Array :
AppDelegate *sc = (AppDelegate *)[UIApplication sharedApplication].delegate;
sc=[[AppDelegate alloc]init];
[DataName addObjectsFromArray:sc.GetNameNSO];
[DataQty addObjectsFromArray:sc.GetQtyNSO];
[DataQty addObjectsFromArray:sc.GetKGNSO];
You should not create the object of App delegate. You should get it by below way.
AppDelegate *sc = (AppDelegate *)[UIApplication sharedApplication].delegate;
[DataName addObjectsFromArray:sc.GetNameNSO];
[DataQty addObjectsFromArray:sc.GetQtyNSO];
[DataQty addObjectsFromArray:sc.GetKGNSO];
Now, sc will have required values. Do not forget to import Appdelegate.h file into the orderdetails.m file.
I have multiple views where I need to handle the network connection of socket.io, so I created singleton class namely MC_SocketHandler. Below is the code of the MC_SocketHandler class.
// MC_SocketHandler.h
#import <Foundation/Foundation.h>
#import "SocketIO.h"
#interface MC_SocketHandler : NSObject <SocketIODelegate>
// SocketIO
//#property (nonatomic) SocketIO *socketConnection;
+ (MC_SocketHandler *) sharedSocketHanderObj;
+ (SocketIO *) initHandShake;
+ (SocketIO *) getSocketConnection;
-(bool) isConnected;
-(void) disConnect;
-(void) fireAgentLeftChat;
#end
// MC_SocketHandler.m
#import "MC_SocketHandler.h"
#import "MC_APIUtility.h"
#implementation MC_SocketHandler
SocketIO *socketConnection = nil;
static MC_SocketHandler *sharedSocketObj = nil;
+ (MC_SocketHandler *) sharedSocketHanderObj {
if (sharedSocketObj == nil)
sharedSocketObj = [[MC_SocketHandler alloc] init];
return sharedSocketObj;
}
+(SocketIO*) initHandShake {
if (socketConnection == nil) {
NSDictionary *headers = [NSDictionary dictionaryWithObjectsAndKeys:[MC_APIUtility getApiToken], #"token", nil];
socketConnection = [[SocketIO alloc] initWithDelegate:(id)self ];
[socketConnection connectToHost:domain onPort:447 withParams:headers];
}
return socketConnection;
}
+ (SocketIO *) getSocketConnection {
return socketConnection;
}
-(bool) isConnected {
if (socketConnection == nil)
return socketConnection.isConnected;
return false;
}
-(void) disConnect {
if (socketConnection != nil && socketConnection.isConnected)
[socketConnection disconnect];
NSLog(#"Disconnected --- %hhd", socketConnection.isConnected );
return;
}
// SocketIO Delegate
-(void) socketIODidConnect:(SocketIO *)socket {
NSLog(#"Socket has Connected....");
}
-(void) socketIO:(SocketIO *)socket didReceiveEvent:(SocketIOPacket *)packet {
NSString *data = packet.data;
NSLog(#"---- didReceoveEvent - data - %#", data);
// Grab data from packet
NSDictionary *dict = packet.dataAsJSON;
NSLog(#"EVENT DATA :- %# DICT :- %#", data, dict);
/*
EVENTS To Listen
onSuccessInit
visitor_info
new_visitor
agent_online
agent_offline
agent_logout
*/
dict = nil;
// Pull out args fro mdict
//NSArray *args = dict[#"args"];
}
-(void) socketIO:(SocketIO *)socket didReceiveMessage:(SocketIOPacket *)packet {
NSLog(#"Rcvd Message - %#", packet.data);
}
-(void) socketIO:(SocketIO *)socket didSendMessage:(SocketIOPacket *)packet {
NSLog(#"Send Msg - %#", packet.dataAsJSON);
}
-(void) socketIO:(SocketIO *)socket onError:(NSError *)error {
NSLog(#"Error - %#", error);
}
-(void) socketIODidDisconnect:(SocketIO *)socket disconnectedWithError:(NSError *)error {
NSLog(#"Disconnected With Error - %#", error);
}
-(void) fireAgentLeftChat {
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
[dict setObject:[MainAppDataObject sharedAppDataObject].activeAgentChatItem.chatSessionId forKey:#"chat_session_id"];
[socketConnection sendEvent:#"agentLeftChat" withData:dict];
return;
}
- (void)dealloc {
socketConnection = nil;
}
#end
Code that I use it in 1 of my views :
// Init SocketIO
SocketIO *socket = [MC_SocketHandler initHandShake];
// Fire Agent Online event
[socket sendEvent:#"setAgentOnline" withData:nil];
Handshake is being done properly, setAgentOnline event is send properly. Other events that I fire are also done properly. BUT,
when socket gets connected thru initHandshake, I believe "Socket has Connected...." should be seen in logs as that is written in socketIODidConnect delegate method. Similarly, I receive event (I see logs of socket.m class), but my delegate method didReceiveEvent is never called. Same way I don't see any logs of any delegate methods.
In initHandShake method only I have set the delegate also :
socketConnection = [[SocketIO alloc] initWithDelegate:(id)self ];
yet why these methods aren't called.
I was also wondering, when I receive events, on different events I got to perform different actions. How will I transfer to particular view (View's obj won't be shared with this to call his method) ? And If I create delegate, then I will have to handle all delegate methods in all views. What's will be the best method to work out with this ? And why this Singleton & delegate methods aren't being linked & called when I have set the delegate. Where am I going wrong ?
Any help, guidance is highly appreciative. Thanks alot.
In SocketIO, you create a SocketIO
Is that right?
In fact called "socketConnection". Am i right?
AT THAT TIME...
you must set the delegate !!!
Essentially, your code must look like this,
socketConnection = make one of these.
socketConnection.delegate = self;
It's possible this is your fundamental problem. I hope it helps!
PS you should, almost certainly, use only properties in iOS development. get rid of your "traditional" variables and use only properties.