I'm working with CoreBluetooth, so in my unit tests I'm mocking all the CB objects so they return what I want. In one of my tests, I mock a CBPeripheral, and stub the delegate method like so:
[[[mockPeripheral stub] andReturn:device] delegate];
The device passed in is my wrapper object which holds on to the peripheral. Later in the test, I call a method on device which then checks:
NSAssert(_peripheral.delegate == self, #"Empty device");
This line is being asserted during the test because _peripheral.delegate != self.
I've debugged through, and made sure that _peripheral is an OCMockObject. Why isn't the stubbed method returning device when the assert checks the _peripheral's delegate?
Here's the detailed code:
#interface Manager : NSObject
- (void)connectToDevice:(Device*)device;
#end
#implementation Foo
- (void)connectToDevice:(Device*)device {
if([device checkDevice]) {
/** Do Stuff */
}
}
#end
#interface Device : NSObject {
CBPeripheral _peripheral;
}
- (id)initWithPeripheral:(CBPeripheral*)peripheral;
#end
#implementation Device
- (id)initWithPeripheral:(CBPeripheral*)peripheral {
self = [super init];
if(self) {
_peripheral = peripheral;
_peripheral.delegate = self;
}
return self;
}
- (BOOL)checkDevice {
NSAssert(_peripheral.delegate == self, #"Empty device");
return YES;
}
#end
#implementation Test
__block id peripheralMock;
beforeAll(^{
peripheralMock = [OCMockObject mockForClass:[CBPeripheral class]];
});
//TEST METHOD
it(#"should connect", ^{
Device *device = [[Device alloc] initWithPeripheral:peripheralMock];
[[[peripheralMock stub] andReturn:device] delegate];
[manager connectToDevice:device];
}
#end
I am not able to reproduce this -- is this what you're doing?
#interface Bar : NSObject <CBPeripheralDelegate>
#property (nonatomic, strong) CBPeripheral *peripheral;
- (void)peripheralTest;
#end
- (void)peripheralTest
{
NSAssert(_peripheral.delegate == self, #"Empty device");
}
// In test class:
- (void)testPeripheral
{
Bar *bar = [Bar new];
id peripheralMock = [OCMockObject mockForClass:CBPeripheral.class];
[[[peripheralMock stub] andReturn:bar] delegate];
bar.peripheral = peripheralMock;
[bar peripheralTest];
}
This test passes for me.
Related
My goal is to achieve synchronized communication to custom Device i.e. next command can be send only when reply is received. Now I'm doing it in this way
Device class implements DeviceDelegate protocol
//Device.h
#class Device;
#protocol DeviceDelegate <NSObject>
- (void)didReciveReplyWithData:(NSData *)data;
#end
#interface Device : NSObject {}
In DeviceViewController implementation:
#interface DeviceViewController()
{
BOOL waitingForReply = false;
}
#end
#implementation DeviceViewController
- (void)sendCommandWithData:(NSData *)data
{
if ( waitingForReply == false)
{
//send command code
waitingForReply = true;
}
}
- (void)didReciveReplyWithData:(NSData *)data
{
//code
waitingForReply = false;
}
#end
but I wish to do it in more elegant way i.e. by using GCD (semaphores?) with blocks (completionHandler?). Any ideas?
PS. Sorry, but I forgot to mention: all commands sended to device while
waitingForReply = true
should be ignored!!!.
Possibly the best approach here would be to create a queue of commands with NSOperationQueue.
Since, presumably, the communication with the device is asynchronous you will have to subclass NSOperation to encapsulate the communication.
#interface DeviceCommandOperation : NSOperation <DeviceDelegate>
#property (nonatomic, assign) BOOL waitingForReply;
#property (nonatomic, copy) NSData *dataToSend;
#property (nonatomic, copy) NSData *dataReceived;
#end
#implementation DeviceCommandOperation
- (instancetype)initWithData:(NSData *)dataToSend
{
self = [super init];
if (self)
{
_dataToSend = [dataToSend copy];
}
return self;
}
- (void)setWaitingForReply:(BOOL)waitingForReply
{
if (_waitingForReply != waitingForReply)
{
[self willChangeValueForKey:#"isExecuting"];
[self willChangeValueForKey:#"isFinished"];
_waitingForReply = waitingForReply;
[self didChangeValueForKey:#"isExecuting"];
[self didChangeValueForKey:#"isFinished"];
}
}
- (void)start
{
self.waitingForReply = YES;
// Simulate sending a command and waiting for response.
// You will need to replace this with your actual communication mechanism.
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
// In reality this call would presumably come from the Device
[self didReceiveReplyWithData:someData];
});
}
- (void)didReceiveReplyWithData:(NSData *)data
{
self.dataReceived = data;
self.waitingForReply = NO;
}
#pragma mark - NSOperation
- (BOOL)isAsynchronous
{
return YES;
}
- (BOOL)isExecuting
{
return _waitingForReply;
}
- (BOOL)isFinished
{
return !_waitingForReply;
}
#end
This operation could then be used from your DeviceViewController (it would probably be better architecturally to have this responsibility elsewhere but that's not the topic of this question).
#interface DeviceViewController ()
#property (nonatomic, strong) NSOperationQueue *operationQueue;
#end
#implementation DeviceViewController
- (NSOperationQueue *)operationQueue
{
if (_operationQueue == nil)
{
_operationQueue = [[NSOperationQueue alloc] init];
}
return _operationQueue;
}
- (void)sendNextCommand
{
NSData *data = // Get data for the next command
[self sendCommandWithData:data];
}
- (void)sendCommandWithData:(NSData *)data
{
NSLog(#"Queueing operation");
DeviceCommandOperation *operation = [[DeviceCommandOperation alloc] initWithData:data];
// The operation's completionBlock gets called on a background queue
[operation setCompletionBlock:^{
NSLog(#"DeviceCommandOperation completed");
// Process operation.dataReceived
[self sendNextCommand];
}];
[self.operationQueue addOperation:operation];
}
#end
This approach will allow you to determine what (if any) command to send next, based on the reply to the previous command.
If you know all of the "commands" you will want to send initially and don't need finer grained control you could create instances of DeviceCommandOperation for each command, set the queue's maxConcurrentOperationCount to 1, and add each DeviceCommandOperation to the queue (in the order you want them to be processed).
I actually try to figure out how inheritance in Objective-C does work. My problem is, that my obj. allways returns "null".
Here is my Code:
Edit: Added rest of code.
// ReportViewController.h
#import <UIKit/UIKit.h>
#import <MessageUI/MessageUI.h>
#import "IAPHelper.h"
#class Report, Category, GADBannerView;
#interface ReportViewController : UIViewController <UIWebViewDelegate,
NSFetchedResultsControllerDelegate> {NSString* _werbung;}
#property (nonatomic, strong) GADBannerView *bannerView;
#property (nonatomic, retain) NSString* werbung;
- (id)initWithReport:(Report *)report category:(Category *)category ;
#end
// ReportViewController.m
#import "ReportViewController.h"
#import "IAPHelper.h"
#interface ReportViewController ()
- (void)loadReport;
- (void)setupFetchRequest;
- (void)resizeNavigationContentViewToHeight:(CGFloat)height;
- (NSString*) werbung;
- (void)setWerbung:(NSString *)newwerbung;
#end
#implementation ReportViewController
#synthesize werbung = _werbung;
-(NSString*) werbung {
return _werbung;
}
- (void)setWerbung:(NSString *)newwerbung {
_werbung= newwerbung;
}
//Werbung ausblenden
NSLog(#"Check for bought products");
if ([_werbung isEqual: #"gekauft"]) {
self.bannerView.hidden = TRUE;
}
- (void)viewDidLoad
{
[super viewDidLoad];
if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 7.0)
{
self.edgesForExtendedLayout=UIRectEdgeNone;
self.navigationController.navigationBar.translucent = NO;
}
//ADMob
if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad) {
if([UIApplication sharedApplication].statusBarOrientation == UIInterfaceOrientationPortrait) {
_bannerView = [[GADBannerView alloc] initWithFrame: CGRectMake(20.0,850.0,728,90 )];}
if([UIApplication sharedApplication].statusBarOrientation == UIInterfaceOrientationLandscapeRight) {
_bannerView = [[GADBannerView alloc] initWithFrame: CGRectMake(-10,615.0,728,90 )];}
if([UIApplication sharedApplication].statusBarOrientation == UIInterfaceOrientationLandscapeLeft) {
_bannerView = [[GADBannerView alloc] initWithFrame: CGRectMake(-10,615.0,728,90 )];}
}
else
_bannerView = [[GADBannerView alloc] initWithFrame: CGRectMake(0,410,320,50 )];
//initWithAdSize:kGADAdSizeBanner];
//initwithframe:CGRectMake(0.0,0.0,320,50 )];
self.bannerView.adUnitID = #„xxxxxxxxxxxxxxxxx“;
self.bannerView.rootViewController = self;
GADRequest *request = [GADRequest request];
// Enable test ads on simulators.
[self.view addSubview:(_bannerView)];
request.testDevices = #[ GAD_SIMULATOR_ID, #„xxxxxxxxxxxxxxxxxxxxxxx“ ];
[self.bannerView loadRequest:request];
//Werbung ausblenden
NSLog(#"Check for bought products");
if ([_werbung isEqual: #"gekauft"]) {
self.bannerView.hidden = TRUE;
}
NSLog(#"%#",_werbung);
NSLog(#"%#",self.werbung);
}
// IAPHelper.m
#import "IAPHelper.h"
#import <StoreKit/StoreKit.h>
#import "ReportViewController.h"
#interface IAPHelper () <SKProductsRequestDelegate, SKPaymentTransactionObserver>
#end
#implementation IAPHelper
- (id)initWithProductIdentifiers:(NSSet *)productIdentifiers
{
//self = [super init];
if ((self = [super init])) {
// Store product identifiers
_productIdentifiers = productIdentifiers;
// Check for previously purchased products
_purchasedProductIdentifiers = [NSMutableSet set];
[[SKPaymentQueue defaultQueue] addTransactionObserver:self];
for (NSString * productIdentifier in _productIdentifiers) {
BOOL productPurchased = [[NSUserDefaults standardUserDefaults] boolForKey:productIdentifier];
if (productPurchased) {
[_purchasedProductIdentifiers addObject:productIdentifier];
NSLog(#"Previously purchased: %#", productIdentifier);
if ([productIdentifier isEqual:#"XXXXXXXXXXXXXXXXXXXXXXXXXX"]) {
ReportViewController *rvc = [[ReportViewController alloc] init];
rvc.werbung = #"gekauft";
NSLog(#"werbung gekauft!");
NSLog(#"%#", rvc.werbung); <- log's #"gekauft";
} else {
NSLog(#"Not purchased: %#", productIdentifier);
}
}
[[SKPaymentQueue defaultQueue] addTransactionObserver:self];
}}
return self;
}
My question is: What I did wrong? Maybe you got a good tutorial for me too?
EDIT: You were right, it was not about inheritance. My solution is working with UserDefaults.
This isn't a question of inheritance — inheritance governs what behaviour a subclass will acquire from its parent. The issue seems to be one of instances.
ReportViewController is a class. So it's not an actual actor. It's just the description of how any ReportViewControllers that are created will act. Like a constitution.
When you call alloc] init] you create one new instance of the view controller. You then set the advertisement as bought on that instance. You don't put the instance anywhere or otherwise keep hold of it. That instance therefore ceases to exist.
Elsewhere, in a completely different instance, you check the advertisement value. Nobody has told that instance anything. So you see the nil values.
Think of it exactly the same as NSString. In the code below, should stringB change value?
NSMutableString *stringA = [[NSMutableString alloc] init];
NSMutableString *stringB = [[NSMutableString alloc] init];
[stringA appendString:#"Mo' string for ya'"];
The ReportViewController that you're using to set the werbung value is not the same controller where you're checking the value. The one where you're doing the assignment is local to the method where it's being allocated.
I use OCMock to test out singleton methods. I get "no such method exists in the mocked class." error for testSingleton method and infinite loop (the screenshot, the spinning indicator) for testSingletonWithBlock method
EDIT:
download sample project here
https://drive.google.com/file/d/0B-iP0P7UfFj0LVFpWWpPb3RDZFU/edit?usp=sharing
Here is my implementation
manager:
#implementation Manager
+ (Manager *)sharedManager {
static Manager *instance;
dispatch_once_t predicate;
dispatch_once(&predicate, ^{
instance = [Manager new];
});
return instance;
}
- (int)getOne {
return 1;
}
- (void)success:(BOOL)success completion:(void(^)(void))completion failure:(void(^)(void))failure {
success ? completion() : failure();
}
view controller:
- (void)manager_printOne {
int num = [[Manager sharedManager] getOne];
NSLog(#"number is: %d", num);
}
- (void)manager_success:(BOOL)success completion:(void(^)(void))completion failure:(void(^)(void))failure {
[[Manager sharedManager] success:success completion:completion failure:failure];
}
test view controller:
#interface coreDataTestTests : XCTestCase
#property (nonatomic, strong) id mockManager;
#property (nonatomic, strong) ViewController *viewController;
#end
#implementation coreDataTestTests
- (void)setUp
{
[super setUp];
self.viewController = [ViewController new];
//for singleton
self.mockManager = [Manager createNiceMockManager];
}
- (void)tearDown
{
[super tearDown];
self.viewController = nil;
//Note: singleton need both, retain counts = 2
self.mockManager = nil;
[Manager releaseInstance];
}
- (void)testSingleton {
NSLog(#"testSingleton");
OCMStub([self.mockManager getOne]).andReturn(2);
[self.viewController manager_printOne];
}
- (void)testSingletonWithBlock {
NSLog(#"testSingletonWithBlock");
[[[[self.mockHelper stub] ignoringNonObjectArgs] andDo:^(NSInvocation *invocation) {
void(^block)(void);
[invocation getArgument:&block atIndex:3];
block();
}] success:0 completion:[OCMArg any] failure:[OCMArg any]];
[self.viewController manager_success:NO completion:^{
NSLog(#"completion");
} failure:^{
NSLog(#"failure");
}];
}
#end
manager category for unit test:
static Manager *mockManager = nil;
#implementation Manager
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-protocol-method-implementation"
+ (Manager *)sharedManager {
if (mockManager) {
return mockManager;
}
return invokeSupersequentNoParameters();
}
#pragma clang diagnostic pop
+(id)createMockManager {
mockManager = [OCMockObject mockForClass:[Manager class]];
return mockManager;
}
+(id)createNiceMockManager {
mockManager = [OCMockObject niceMockForClass:[Manager class]];
return mockManager;
}
+(void)releaseInstance {
mockManager = nil;
}
Rather than creating a category, you could just stub sharedManager and return a nice mock.
- (void)setUp
{
[super setUp];
self.viewController = [ViewController new];
//for singleton
id classMockManager = OCClassMock([Manager class]);
OCMStub([classMockManager sharedManager]).andReturn(classMockManager);
self.mockManager = classMockManager;
}
I don't have an environment up to test this just this moment, but this strategy should work. Note that this is OCMock3 syntax. See http://ocmock.org/reference/#mocking-class-methods
In your description above you write "manager category for unit test", but the implementation that follows is not a category, it's an actual implementation of the Manager class, i.e. the code reads
#implementation Manager
and not
#implementation Manager(unitTests)
It seems that the test code uses this second implementation of Manager and that implementation does not have a getOne method. So the mock is right to complain; the implementation of Manager it sees does not have the method and hence it can't stub it.
I believe you can fix your test by making the implementation a category. As far as I know it is possible to override a class method (sharedManager in your case) in a category, but it's a bit dicey to do so. The approach described by Ben Flynn is better.
I am trying to bypass the login page to welcome page if the person already have a token. I am using basic auth and afnetworking for the api call. Once the user logs the username&password, I base 64 the info and get a token. I save the token in a nsobject class where I create a token singleton and a simple is logged in method where it checks if the user has the token or not. But for some reason I always see the login page(meaning it skips my condition method). If any one can point out where I am making the mistake, it would be great.
This is class where I create the login singleton
CredentialStore.h
#import <Foundation/Foundation.h>
#import "LoginInfo.h"
#interface CredentialStore : NSObject
-(BOOL)isLoggedIn;
#property (nonatomic) LoginInfo *loginInfo;
+(id)sharedStore;
#end
CredentialStore.m
#import "CredentialStore.h"
static CredentialStore *sharedInsance;
#implementation CredentialStore
+(id)sharedStore
{
if (!sharedInsance) {
sharedInsance = [[CredentialStore alloc] init];
}
return sharedInsance;
}
- (BOOL)isLoggedIn {
return (self.loginInfo.authToken != nil);
}
#end
this is my authenticationapimanager class
AuthAPIManager.h
#import "AFHTTPSessionManager.h"
#interface AuthAPIManager : AFHTTPSessionManager
+ (id)sharedManager;
#end
AuthAPIManager.m
#import "AuthAPIManager.h"
#import "CredentialStore.h"
#define BASE_URL #"http://Url"
#define Base_Proxy #"http://Url"
static AuthAPIManager *sharedManager;
#implementation AuthAPIManager
//Setup the singleton to use throught the life of the application
+(id)sharedManager
{
if (!sharedManager) {
sharedManager = [[AuthAPIManager alloc] initWithBaseURL:[NSURL URLWithString:Base_Proxy]];
}
return sharedManager;
}
-(id)initWithBaseURL:(NSURL *)url
{
self = [super initWithBaseURL:url];
if (self) {
}
return self;
}
#end
this is the class where I save the login information
LoginInfo.h
#import <Foundation/Foundation.h>
#interface LoginInfo : NSObject
#property(nonatomic,copy)NSNumber *AccountId;
#property(nonatomic,copy)NSString *DeviceType;
#property(nonatomic,copy)NSString *HardwareId;
#property(nonatomic,copy)NSString *NickName;
#property(nonatomic,copy)NSString *authToken;
-(id)initWithDictionary:(NSDictionary *)dictionary;
#end
LoginInfo.m
#import "LoginInfo.h"
#implementation LoginInfo
-(id)initWithDictionary:(NSDictionary *)dictionary
{
self =[super init];
if (self) {
self.AccountId = [dictionary objectForKey:#"AccountId"];
self.DeviceType = [dictionary objectForKey:#"DeviceType"];
self.HardwareId = [dictionary objectForKey:#"HardwareId"];
self.NickName = [dictionary objectForKey:#"NickName"];
}
return self;
}
and in my view controller, in viewwillappearmethod, I check if the user has a token or not but this is the part I am having problems with
LoginViewController.m
#interface LoginViewController ()
#property (nonatomic,strong) CredentialStore *credentialStore;
#end
implementation LoginViewController
#pragma mark - UIViewController
-(void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self checkIfThePersonLoggedIn];
}
-(void)checkIfThePersonLoggedIn
{
CredentialStore *credStore = [CredentialStore sharedStore];
// //check to see if the use has the token already
if (credStore.loginInfo.authToken != nil) {
[self.loginButton setHidden:YES];
[self performSegueWithIdentifier:#"welcomeViewSegue" sender:self];
}
else [self.loginButton setHidden:NO];
NSLog(#"checkForToken - loginviewcontroller");
}
and in my getTokenRequest this is how I save the token in the event of success
NSString *authToken = [responseObject objectForKey:#"Token"];
CredentialStore *credStore = [CredentialStore sharedStore];
LoginInfo * loginInfo = credStore.loginInfo;
loginInfo.authToken = authToken;
NSLog(#"this is the token here %#",authToken);
[self performSegueWithIdentifier:#"welcomeViewSegue" sender:self];
I appreciate the help.
this is where I instantiate login info
- (IBAction)login:(id)sender
{
[_usernameTextField resignFirstResponder];
[SVProgressHUD show];
id LoginParams =#{
#"NickName" : self.usernameTextField.text,
#"password" :_StoreIdentifierForVendor,
#"DeviceType" :_DeviceModel
};
[[AuthAPIManager sharedManager]POST:#"/url" parameters:LoginParams success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(#"response: %#", responseObject);
LoginInfo *loginInfo = [[LoginInfo alloc] initWithDictionary:responseObject];
CredentialStore *credStore = [CredentialStore sharedStore];
credStore.loginInfo = loginInfo;
[self getToken];
}failure....
Is it OK to put the CLLocationManager Delegate methods in a Singleton class which is a subclass of NSObject instead of UIViewController? I would like to do this as Id like to invoke the Singleton from the App Delegate and start getting the coordinates while the UI is loading
I have a locationcontroller class and I have the following initialization code in it
static locationController *sharedLocController = NULL;
+(locationController *) getSharedController
{
if (sharedLocController !=nil)
{
NSLog(#"locationController has already been created.....");
return sharedLocController;
}
#synchronized(self)
{
if (sharedLocController == nil)
{
sharedLocController = [[self alloc] init];
}
}
return sharedLocController;
}
//==============================================================================
+(id)alloc
{
#synchronized([locationController class])
{
NSAssert(sharedLocController == nil, #"Attempted to allocate a second instance of a sharedLocMgr singleton.");
sharedLocController = [super alloc];
return sharedLocController;
}
return nil;
}
//==============================================================================
-(id)init
{
self = [super init];
if(sharedLocController !=nil)
{
if(!self.locMgr)
{
[self initLocationManager];
}
}
return sharedLocController;
}
//==============================================================================
-(void)initLocationManager
{
self.locMgr = [[CLLocationManager alloc] init];
self.locMgr.delegate = self;
self.locMgr.distanceFilter = kCLDistanceFilterNone;
self.locMgr.desiredAccuracy = kCLLocationAccuracyBest;
[self.locMgr startUpdatingLocation];
NSLog(#"location manager object %#", locMgr);
}
Problem is that the self.locMgr object is always null.
Thanks
Get rid of your alloc function. It isn't necessary and is returning nil, which means this init call is getting called on a nil pointer:
sharedLocController = [[self alloc] init];
Yes, it's fine to put them in an NSObject subclass, which might be a singleton. I use something like:
#interface MyCLController : NSObject <CLLocationManagerDelegate> {
CLLocationManager *locationManager;
}
...
#end
though in case it's not a singleton.