I can not compile this code:
[verify(mockedContext) deleteObject:item1];
[verify(mockedContext) deleteObject:item2];
[verify(mockedContext) save:anything()];<--compilation error for conversion id to NSError**
However I'm able to pass compilation in similar case with given macros with additional syntax:
[[given([mockedContext save:nil]) withMatcher:anything()] willReturn:nil];
Are there anything to help me pass compilation with verify?
Here is compilation error:
Implicit conversion of an Objective-C pointer to 'NSError *__autoreleasing *' is disallowed with ARC
I assume the save: method on the 'mockedContext' takes a pointer-to-pointer to NSError.
So actually, the NSError must be seen as an extra return value of the save:method. This means that you should rather setup an expectation in the first place.
I worked out a small example:
We start with the Context protocol with a simple method taking an NSError**.
#protocol Context <NSObject>
- (id)doWithError:(NSError *__autoreleasing *)err;
#end
Next is a class using this protocol, much like your SUT. I called it ContextUsingClass
#interface ContextUsingClass : NSObject
#property (nonatomic, strong) id<Context> context;
#property BOOL recordedError;
- (void)call;
#end
#implementation ContextUsingClass
- (void)call {
NSError *error;
[self.context doWithError:&error];
if (error) {
self.recordedError = YES;
}
}
#end
As you can see, when the context method doWithError: returns an error, the recordedError property is set to YES. This is something we can expect to be true or false in our test. The only problem is, how do we tell the mock to result in an error (or to succeed without error)?
The answer is fairly straight forward, and was almost part of your question: we pass an OCHamcrest matcher to the given statement, which in turn will set the error for us through a block. Bear with me, we'll get there. Let's first write the fitting matcher:
typedef void(^ErrorSettingBlock)(NSError **item);
#interface ErrorSettingBlockMatcher : HCBaseMatcher
#property (nonatomic, strong) ErrorSettingBlock errorSettingBlock;
#end
#implementation ErrorSettingBlockMatcher
- (BOOL)matches:(id)item {
if (self.errorSettingBlock) {
self.errorSettingBlock((NSError * __autoreleasing *)[item pointerValue]);
}
return YES;
}
#end
This matcher will call the errorSettingBlock if it has been set, and will always return YES as it accepts all items. The matchers sole purpose is to set the error, when the test asks as much. From OCMockito issue 22 and it's fix, we learn that pointer-to-pointers are wrapped in NSValue objects, so we should unwrap it, and cast it to our well known NSError **
Now finally, here is how the test looks:
#implementation StackOverFlowAnswersTests {
id<Context> context;
ContextUsingClass *sut;
ErrorSettingBlockMatcher *matcher;
}
- (void)setUp {
[super setUp];
context = mockProtocol(#protocol(Context));
sut = [[ContextUsingClass alloc] init];
sut.context = context;
matcher = [[ErrorSettingBlockMatcher alloc] init];
}
- (void)testContextResultsInError {
matcher.errorSettingBlock = ^(NSError **error) {
*error = [NSError errorWithDomain:#"dom" code:-100 userInfo:#{}];
};
[[given([context doWithError:nil]) withMatcher:matcher] willReturn:nil];
[sut call];
assertThatBool(sut.recordedError, is(equalToBool(YES)));
}
- (void)testContextResultsInSuccess {
[[given([context doWithError:nil]) withMatcher:matcher] willReturn:nil];
[sut call];
assertThatBool(sut.recordedError, is(equalToBool(NO)));
}
#end
Conclusion
When you call methods within your SUT which are returning errors through pointer-to-pointers, you should probably test for the different possible outcomes, rather than just verifying if the method has been called.
If your SUT is ignoring the error, then let the block you pass into the matcher keep a boolean flag to indicate that it was called like so:
- (void)testNotCaringAboutTheError {
__block BOOL called = NO;
matcher.errorSettingBlock = ^(NSError **error) {
called = YES;
};
[[given([context doWithError:nil]) withMatcher:matcher] willReturn:nil];
[sut call];
assertThatBool(called, is(equalToBool(YES)));
}
Or with simple verification:
- (void)testWithVerifyOnly {
[sut call];
[[verify(context) withMatcher:matcher] doWithError:nil];
}
PS: Ignoring errors is probably something you don't want to do...
Related
I have a service that I'm currently writing a unit test for. The code works as expected, but I'm getting a strange retain cycle warning.
[self.myService doSomethingCoolWithCompletionBlock:^(MyResponseObject *obj) {
XCTAssertNil(obj, #"obj should be nil");
}];
The XCTAssertNil(obj, #"obj should be nil"); line shows a warning in Xcode Capturing 'self' strongly in this block is likely to lead to a retain cycle.
If I change the code to the following, the warning is removed:
__weak MyService *weakService = self.myService;
[weakService doSomethingCoolWithCompletionBlock:^(MyResponseObject *obj) {
XCTAssertNil(obj, #"obj should be nil");
}];
I am using self.someService in other unit tests, and never had this issue. Anyone experienced this before?
EDIT
I have another test that has the following:
[self.myService doSomethingElseCoolWithCompletionBlock:(NSArray *results) {
XCTestAssertNotNil(results, #"results should not be nil");
}];
This doesn't give me a warning. The only difference I see is that this is checking an array, and the other is checking an object of a specific type.
assert it is macros and used self inside.
so you need create local variable with name self.
__weak id weakSelf = self;
self.fooBlock = ^{
id self = weakSelf;
XCTAssert(YES);
};
Don't do this:
#interface MyCoolTests : XCTestCase
#property (retain) id myService;
#end
#implementation MyCoolTests
-(void)testCoolness{
self.myService = [MyService new];
self.myService.callback = ^{
XCTAssert(YES);
};
// ...
}
#end
Do this:
#interface MyCoolTests : XCTestCase
#end
#implementation MyCoolTests
-(void)testCoolness{
id myService = [MyService new];
myService.callback = ^{
XCTAssert(YES);
};
// ...
}
#end
It's a limitation of XCTTestCase and it probably catches people when using the setup method.
I have read about __bridge, _bridge_retain and _bridge_transfer and did some experiments. However the output does not coincide with what I was expecting. In particular, I have the following code:
#interface ViewController ()
#property (nonatomic, strong) NSString *test;
#end
#implementation ViewController
CFStringRef cfString;
- (void)viewDidLoad
{
[super viewDidLoad];
self.test = #"123";
cfString = (__bridge CFStringRef)self.test;
self.test = nil;
}
- (void)viewDidAppear:(BOOL)animated
{
NSLog(#"%#", cfString);
NSLog(#"%#", self.test);
}
I expect the program to crash, based on the following reasoning: _bridge does not transfer ownership, so while casting self.test to cfString, there is no retainCount increment. Once self.test is set to nil, ARC will step in and dealloc the string. So that portion of memory is freed, but cfString is still pointing there, resulting in a pointer bad access exception. In contrast to my reasoning, the output is 123 and null, and of course the program does not crash.
Moreover, if I replace
self.test = nil;
with
CFRelease(cfString);
I expect the program to crash as well due to a similar reasoning. Even stranger is that the output is now 123 and 123.
Can anyone kindly elaborate why? Btw, the term ownership always troubles me, some explanation will be greatly appreciated.
Your problem is that you're using a constant string. This is put straight into the programs memory so the reference is unexpectedly remaining valid despite the fact that it shouldn't. Use something less constant than a constant string and your program will brake like you think.
The problem is that you base your example on a literal NSString value.
In objective-C, constant NSString (constant values known at compile time) are never released. In fact, their main memory managment methods are like:
+ (id)allocWithZone:(NSZone *)zone {
id _uniqueInstance = [self _singletonInstanceOfClass];
if( _uniqueInstance == nil )
_uniqueInstance = [super allocWithZone:zone];
return _uniqueInstance;
}
- (id)copyWithZone:(NSZone *)zone {
(void)zone;
return self;
}
- (id)retain {
return self;
}
- (NSUInteger)retainCount {
return NSUIntegerMax; // denotes an object that cannot be released
}
- (oneway void)release {
//do nothing
return;
}
- (id)autorelease {
return self;
}
As you can see, releasing them is not possible.
Based on the documentation of OCMock, it should be possible to test class methods, but I may be misunderstanding what is and isn't possible with OCMock. Take the following example method I wish to test:
- (void)methodToTest {
[SVProgressHUD dismiss];
}
I'd like to test that dismiss is called on SVProgressHUD. I currently use the following test, but that doesn't seem to do the trick.
- (void)testMethodToTest {
// Create Mock Progress HUD
id mockProgressHUD = OCMClassMock([SVProgressHUD class]);
// Configure Mock Progress HUD
OCMStub(ClassMethod([mockProgressHUD dismiss]));
// Invoke Method to Test
[object methodToTest];
OCMVerify([mockProgressHUD dismiss]);
}
Is it possible with OCMock to test whether dismiss is called on SVProgressHUD?
Your test passes for me. Perhaps it's something subtle in your implementation? What is object? Here's my simplified version:
#interface Foo : NSObject
+ (void)dismiss;
#end
#implementation Foo
+ (void)dismiss
{
NSLog(#"Dismiss!");
}
#end
#interface Bar : NSObject
- (void)methodToTest;
#end
#implementation Bar
- (void)methodToTest
{
[Foo dismiss];
}
#end
- (void)testClassMock
{
Bar *bar = [Bar new];
id mockFoo = OCMClassMock([Foo class]);
OCMStub(ClassMethod([mockFoo dismiss]));
[bar methodToTest];
OCMVerify([mockFoo dismiss]);
}
I have trouble implementing a Key-Value Observer at my attempt to follow the MVC pattern. I have a controller class, a model class and a view class. I update my model from the controller class and I want to put a key value observer in my view class to monitor when a NSMutableArray changes in model (like through addObject) and then redraw itself automatically. I used answer in this thread to guide me: How to add observer on NSMutableArray?
Code so far:
From my Scene (using sprite kit if it matters). Setting of letters will be done from Ctrl class, this is just to test.
BarCtrl *barCtrl = [[BarCtrl alloc] init];
BarModel *barModel = [[BarModel alloc] init];
BarView *barView = [[BarView alloc] init];
barCtrl.barModel = barModel;
barCtrl.barView = barView;
barView.barModel = barModel;
ScrabbleDeck *sd = [[ScrabbleDeck alloc] init];
if([barModel addLetter:[sd getLetter] onSide:BarModelSideRight])
NSLog(#"Added letter");
BarModel.h
#import <Foundation/Foundation.h>
#import "Letter.h"
typedef NS_ENUM(int, BarModelSide) {
BarModelSideLeft,
BarModelSideRight
};
#interface BarModel : NSObject
#property (nonatomic, strong) NSMutableArray *addedLetters;
- (instancetype)init;
- (BOOL) addLetter: (Letter*) letter onSide: (BarModelSide) side;
#end
BarModel.m
#import "BarModel.h"
#interface BarModel ()
#property (nonatomic) int capacity;
#end
#implementation BarModel
- (instancetype)init
{
self = [super init];
if (self) {
self.capacity = letterCapacity;
_addedLetters = [[NSMutableArray alloc] init];
}
return self;
}
// We'll use automatic notifications for this example
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key
{
if ([key isEqualToString:#"arrayLetter"]) {
return YES;
}
return [super automaticallyNotifiesObserversForKey:key];
}
- (BOOL) addLetter: (Letter*) letter onSide: (BarModelSide) side{
if([_addedLetters count] > _capacity){
return FALSE;
}
switch (side) {
case BarModelSideLeft:
[_addedLetters insertObject:letter atIndex:0];
return TRUE;
break;
case BarModelSideRight:
[_addedLetters addObject:letter];
return TRUE;
break;
default:
return FALSE;
break;
}
}
// These methods enable KVC compliance
- (void)insertObject:(id)object inDataAtIndex:(NSUInteger)index
{
self.addedLetters[index] = object;
}
- (void)removeObjectFromDataAtIndex:(NSUInteger)index
{
[self.addedLetters removeObjectAtIndex:index];
}
- (id)objectInDataAtIndex:(NSUInteger)index
{
return self.addedLetters[index];
}
- (NSArray *)dataAtIndexes:(NSIndexSet *)indexes
{
return [self.addedLetters objectsAtIndexes:indexes];
}
- (NSUInteger)countOfData
{
return [self.addedLetters count];
}
#end
BarView.h
#import <SpriteKit/SpriteKit.h>
#import "BarModel.h"
#interface BarView : SKSpriteNode
#property (nonatomic, strong) BarModel *barModel;
#end
BarView.m
#import "BarView.h"
#implementation BarView
static char MyObservationContext;
- (instancetype)init
{
self = [super init];
if (self) {
//_barModel = [[BarModel alloc] init];
}
return self;
}
-(void)setBarModel:(BarModel *)barModel{
if(_barModel != barModel)
_barModel = barModel;
[_barModel addObserver:self
forKeyPath:#"arrayLetter"
options:(NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew)
context:&MyObservationContext];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
// Check if our class, rather than superclass or someone else, added as observer
if (context == &MyObservationContext) {
// Check that the key path is what we want
if ([keyPath isEqualToString:#"arrayLetter"]) {
// Verify we're observing the correct object
if (object == self.barModel) {
[self draw:change];
}
}
}
else {
// Otherwise, call up to superclass implementation
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
}
- (void) draw: (NSDictionary*) change{
NSLog(#"KVO for our container property, change dictionary is %#", change);
}
#end
When I ru this I get this "error":
2014-08-31 00:23:02.828 Testing[329:60b] Added letter
2014-08-31 00:23:02.830 Testing[329:60b] An instance 0x17803d340 of class BarModel was deallocated while key value observers were still registered with it. Observation info was leaked, and may even become mistakenly attached to some other object. Set a breakpoint on NSKVODeallocateBreak to stop here in the debugger. Here's the current observation info:
<NSKeyValueObservationInfo 0x17804eb50> (
<NSKeyValueObservance 0x1780cf180: Observer: 0x178111670, Key path: arrayLetter, Options: <New: YES, Old: YES, Prior: NO> Context: 0x100101428, Property: 0x17804eb80>
I tried to follow the instructions in error but can not find where to set break point. Please help me figure this out!
The error is pretty descriptive. You add self as an observer of a BarModel object. At some point that object gets deallocated. But you never remove self as an observer by calling removeObserver:forKeyPath:context:. You need to do that.
First, in setBarModel, make sure to remove self as an observer of the previous value of _barModel.
Next, you probably need to add a dealloc method that does the same thing.
There are multiple problems with the code. In addition to what Tom Harrington said with respect to the specific error that was logged about failing to remove the observation:
You implemented the indexed collection accessors for a (non-existent) property named "data". That is, they have "Data" in their name where the property name should be.
The indexed collection property is addedLetters. So, the indexed collection mutating accessors should be:
- (void)insertObject:(id)object inAddedLettersAtIndex:(NSUInteger)index;
- (void)removeObjectFromAddedLettersAtIndex:(NSUInteger)index;
You don't really need the non-mutating accessors, since you have an array-type public property with a normal getter (i.e. -addedLetters).
By the way, that property is of type NSMutableArray which it should not be. The property should be of type NSArray, backed by an instance variable of type NSMutableArray. That is, the mutability of the type (as opposed to the property) should not be exposed through the public interface. When you do this, you have to manually declare the instance variable (since it should differ from the type of the property and auto-synthesis will get it wrong), make the property copy instead of strong, and implement the setter yourself to do a mutable copy of the passed-in immutable array:
- (void) setAddedLetters:(NSArray*)addedLetters
{
if (addedLetters != _addedLetters)
_addedLetters = [addedLetters mutableCopy];
}
Once you have implemented the indexed collection mutating accessors with the correct names, you must use only those methods to mutate the collection (after initialization). In particular, your -addLetter:onSide: method must not directly operate on the _addedLetters instance variable. This is the part that makes the class KVO-compliant for that property. The mere presence of the indexed collection mutating accessors does not help. They must be used for all actual mutations.
Your implementation of +automaticallyNotifiesObserversForKey: is redundant. Automatic notification is the default.
The BarView class is key-value observing a key path "arrayLetter" on its _barModel object, but that's not the name of the property on BarModel. I suppose you meant to use the key path "addedLetters".
Finally, for proper adherence to MVC design, your view should not have a reference to your model. It should have a reference to the controller. The controller can reflect the model to the view (or, in theory, adapt a model of a different internal design to what the view expects). Or, in a more traditional non-KVO approach, the controller would actually tell the view when something has changed and give it the updated data it should show.
So, your controller could expose its own addedLetters property:
#property (readonly, copy, nonatomic) NSArray* addedLetters;
It could be implemented as a derived property, forwarded through to the _barModel object:
+ (NSSet*)keyPathsForValuesAffectingAddedLetters
{
return [NSSet setWithObject:#"barModel.addedLetters"];
}
- (NSArray*)addedLetters
{
return self.barModel.addedLetters;
}
Then, the view would have a reference to the controller and not the model, and it would key-value observe the "addedLetters" key path on the controller.
As Apple encourages the usage of blocks, and i wanted to do a series of animations, with sound output in between them which is basicly like a todolist, i wanted to implement this using blocks.
unfortunatly AVAudiosplayer doesnt appear to support onCompletion blocks, in the manner UIAnimation does.
So i thought it would be cool to add that support to the AVAudioplayer.
so what ive dont is this
header
#import <AVFoundation/AVFoundation.h>
#interface AVAudioPlayer (AVAudioPlayer_blockSupport)
typedef void(^AVPlaybackCompleteBlock)(void);
#property (nonatomic, copy) AVPlaybackCompleteBlock block;
-(id)initWithContentsOfURL:(NSURL*)pathURL error:(NSError**)error onCompletion:(AVPlaybackCompleteBlock) block;
-(void)setBlock:(AVPlaybackCompleteBlock)block;
-(AVPlaybackCompleteBlock)block;
-(void) executeBlock;
#end
and the m file
#import "AVAudioPlayer+blocks.h"
#implementation AVAudioPlayer (AVAudioPlayer_blockSupport)
-(id)initWithContentsOfURL:(NSURL *)pathURL error:(NSError **)error onCompletion:(AVPlaybackCompleteBlock )block {
self = [[AVAudioPlayer alloc] initWithContentsOfURL:pathURL error:error];
self.block = block;
return self;
}
-(void)setBlock:(AVPlaybackCompleteBlock)block {
self.block = block;
}
-(AVPlaybackCompleteBlock)block {
return self.block;
}
-(void) executeBlock {
if (self.block != NULL) {
self.block();
}
}
#end
after doing this, i thought i should be able to create a new audioplayer like this:
player = [[AVAudioPlayer alloc] initWithContentsOfURL:pathURL error:&error onCompletion:block];
this appears to be working.
now in the delegate will try to execute the block attached.
if (localPlayer.block) {
[localPlayer executeBlock];
}
unfortunately when i try to run the code, it appears to be looping infinitely. I wanted to use synthesize instead, but thats not for category use...
If i dont implement that Method im stuck with '-[AVAudioPlayer setBlock:]: unrecognized selector sent to instance which makes sense, since there is no method with that name.
i found this Block references as instance vars in Objective-C so i thought i should be able to attach the additional property(my Block) to the AudioPlayer.
I figured it out, i needed to use
objc_setAssociatedObject(self, &defaultHashKey, blocked, OBJC_ASSOCIATION_COPY_NONATOMIC);
to store and access the property. maybe thats what jere meant with, i have tot ake care of the memory management myself.
-(void)setBlock:(AVPlaybackCompleteBlock)blocked {
objc_setAssociatedObject(self, &defaultHashKey, blocked, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
-(AVPlaybackCompleteBlock)block {
return objc_getAssociatedObject(self, &defaultHashKey) ;
}