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.
Related
I'm making an app for practice. This app shares with a simple model through AppDelegate. To manipulate the model, I got an NSDictionary object from the model and allocate it to a viewController property. but It seems too verbose.
// viewController.h
#property (nonatomic, strong) NSMutableDictionary *bookDetail;
#property (nonatomic, strong) bookModel *modelBook;
// viewController.m
- (void)setLabel {
self.label_name.text = self.bookDetail[#"name"];
self.label_author.text = self.bookDetail[#"author"];
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
id appDelegate = [[UIApplication sharedApplication] delegate];
self.modelBook = [appDelegate modelBook];
self.bookDetail = self.modelBook.bookList[self.modelBook.selectedId];
[self setLabel];
self.editMod = NO;
}
- (IBAction)editSave:(id)sender {
if (self.editMod == NO) {
....
[self.nameField setText:self.bookDetail[#"name"]];
[self.authorField setText:self.bookDetail[#"author"]];
....
} else {
self.bookDetail = [#{#"name" : self.nameField.text,
#"author" : self.authorField.text} mutableCopy];
[self setLabel];
....
}
}
#end
*bookDetail work like a copy of self.modelBook.bookList[self.modelBook.selectedId] not a reference. Using self.modelBook.bookList[self.modelBook.selectedId] works well, but I don't want to. How Can I simplify this code?
*bookDetail work like a copy of self.modelBook.bookList[self.modelBook.selectedId] not a reference. Using self.modelBook.bookList[self.modelBook.selectedId] works well, but I don't want to.
Your question is not clear to me so this might be wrong, but hopefully it helps.
bookDetail is not a "copy" in the usual sense, rather it is a reference to the same dictionary that self.modelBook.bookList[self.modelBook.selectedId] references at the time the assignment to bookDetail is made.
Given that you say that using the latter "works well" is sounds as though self.modelBook.selectedId is changing and you expected bookDetail to automatically track that change and now refer to a different dictionary. That is not how assignment works.
How Can I simplify this code?
You could add a property to your modelBook class[1], say currentBook, which returns back bookList[selectedID] so each time it is called you get the current book. In your code above you then use self.modelBook.currentBook instead of self.bookDetail and can remove the property bookDetail as unused (and incorrect).
HTH
[1] Note: this should be called ModelBook to follow naming conventions. Have you noticed the syntax coloring is incorrect? That is because you haven't followed the convention.
Create the shared instance of BookModel then you can access it anywhere:
Write this in bookModel:
+ (instancetype)sharedInstance
{
static bookModel *sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[bookModel alloc] init];
// Do any other initialisation stuff here
});
return sharedInstance;
}
Then you can access this like bookModel.sharedInstance.bookList
I have a Worker class, its header:
#interface Worker : NSObject {
// instance variable `task`
MyTask *task;
}
#end
its initialiser method is like this:
#implementation Worker
-(id)initWithName:(NSString*)name{
self = [super init];
if (self) {
// I need to use mocked task in my test case
task = [[MyTask alloc] init];
...
...
}
return self
}
-(void)doWork{
[task start];
}
I want to unit test this class, I am using OCMock library to do the mock. I know I can create a mocked task by:
id mockedTask = OCMClassMock([MyTask class]);
But, how can I inject this mocked task into my Worker instance in test case ? I mean task is created in initialiser method, when I create a Worker instance in my test class:
-(void)setup{
// how to inject the mocked task in to this workerUnderTest
Worker *workerToTest = [[Worker alloc] initWithName:#"John"];
}
I need the mocked task to be used in test. How can I achieve it?
==== My Test case with #iSashok's answer (it doesn't work) ====
#interface WorkerTestCase : XCTestCase
#end
#implementation WorkerTestCase{
Worker *workerToTest;
id mockedTask;
}
- (void)setUp {
[super setUp];
workerToTest = [[Worker alloc] initWithName:#"John"];
mockedTask = OCMClassMock([MyTask class]);
OCMStub([workerToTest task]).andReturn(mockedTask);
}
-(void) testDoWork{
[workerToTest doWork];
// it fails. method is not invoked!
OCMVerify([mockedTask start]);
}
...
#end
As you see above, my test case fails, it complains that there is no invokation on mockedTask object. But when I set breakpoints to real class implementation, the [task start] is invoked, it indicates the mockedTask is not injected successfully.
You can inject it using OCMStub() like below and you need declare task ivar as a property
#interface Worker : NSObject
#property(nonatomic,strong)MyTask *task;
#end
//
id mockedTask = OCMClassMock([MyTask class]);
OCMStub([workerToTest task]).andReturn(mockedTask);
You method will look like
-(void)setup{
// how to inject the mocked task in to this workerUnderTest
Worker *workerToTest = [[Worker alloc] initWithName:#"John"];
id mockedTask = OCMClassMock([MyTask class]);
OCMStub([workerToTest task]).andReturn(mockedTask);
}
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 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...
Not sure on the behavior, because I suspect I am getting a deadlock,
I have a class with multiple objects - each object creates a queue with the same name. I'm not sure if GCD is reusing the same queue between the objects or if they just share the same name.
For instance
#interface MyClass
-(void)doSomeWork
#property (nonatomic,strong) dispatch_queue_t myQueue;
#end
#implementation MyClass
-(id)init
{
self = [super init];
self.myQueue = dispatch_queue_create("MyQueue",DISPATCH_QUEUE_SERIAL);
return self;
}
-(void)doSomeWork
{
dispatch_async(self.myQueue,^{
// some long running work
});
}
#end
#interface SomeClassWhichCreatesALotOfObjects
#end
#implementation SomeClassWhichCreatesALotOfObjects
-(void)someMethod
{
for(int i = 0; i < 10000; i++)
{
MyClass *object = [MyClass new];
[object doSomeWork]; // are these running in serial to each other or are each offset to the queue their object has created? Can't understand from the debugger
}
}
#end
As Apple's documentation states, the label is:
A string label to attach to the queue to uniquely identify it in debugging tools such as Instruments...
It is used as a hint, nothing more.
EDIT
Here's the code you want for using a shared queue.
+ (dispatch_queue_t)sharedQueue
{
static dispatch_queue_t sharedQueue;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedQueue = dispatch_queue_create("MyQueue", NULL);
});
return sharedQueue;
}
dispatch_queue_create does exactly what the name suggests, it creates a dispatch queue. The label you give it is not required to be unique, it is just used for debugging purposes.