I have a number of Objective-C classes organized in an inheritance hierarchy. They all share a common parent which implements all the behaviors shared among the children. Each child class defines a few methods that make it work, and the parent class raises an exception for the methods designed to be implemented/overridden by its children. This effectively makes the parent a pseudo-abstract class (since it's useless on its own) even though Objective-C doesn't explicitly support abstract classes.
The crux of this problem is that I'm unit testing this class hierarchy using OCUnit, and the tests are structured similarly: one test class that exercises the common behavior, with a subclass corresponding to each of the child classes under test. However, running the test cases on the (effectively abstract) parent class is problematic, since the unit tests will fail in spectacular fashion without the key methods. (The alternative of repeating the common tests across 5 test classes is not really an acceptable option.)
The non-ideal solution I've been using is to check (in each test method) whether the instance is the parent test class, and bail out if it is. This leads to repeated code in every test method, a problem that becomes increasingly annoying if one's unit tests are highly granular. In addition, all such tests are still executed and reported as successes, skewing the number of meaningful tests that were actually run.
What I'd prefer is a way to signal to OCUnit "Don't run any tests in this class, only run them in its child classes." To my knowledge, there isn't (yet) a way to do that, something similar to a +(BOOL)isAbstractTest method I can implement/override. Any ideas on a better way to solve this problem with minimal repetition? Does OCUnit have any ability to flag a test class in this way, or is it time to file a Radar?
Edit: Here's a link to the test code in question. Notice the frequent repetition of if (...) return; to start a method, including use of the NonConcreteClass() macro for brevity.
Here's a simple strategy that worked for me. Just override invokeTest in your AbstractTestCase as follows:
- (void) invokeTest {
BOOL abstractTest = [self isMemberOfClass:[AbstractTestCase class]];
if(!abstractTest) [super invokeTest];
}
You could also override + (id)defaultTestSuite method in your abstract TestCase class.
+ (id)defaultTestSuite {
if ([self isEqual:[AbstractTestCase class]]) {
return nil;
}
return [super defaultTestSuite];
}
It sounds like you want a parameterized test.
Parameterized tests are great whenever you want to have a large number of tests with the same logic but different variables. In this case, the parameter to your test would be the concrete tested class, or possibly a block that will create a new instance of it.
There's an article about implementing parameterized testing in OCUnit here. Here's an example of applying it to testing a class hierarchy:
#implementation MyTestCase {
RPValue*(^_createInstance)(void);
MyClass *_instance;
}
+ (id)defaultTestSuite
{
SenTestSuite *testSuite = [[SenTestSuite alloc] initWithName:NSStringFromClass(self)];
[self suite:testSuite addTestWithBlock:^id{
return [[MyClass1 alloc] initWithAnArgument:someArgument];
}];
[self suite:testSuite addTestWithBlock:^id{
return [[MyClass2 alloc] initWithAnotherArgument:someOtherArgument];
}];
return testSuite;
}
+ (void)suite:(SenTestSuite *)testSuite addTestWithBlock:(id(^)(void))block
{
for (NSInvocation *testInvocation in [self testInvocations]) {
[testSuite addTest:[[self alloc] initWithInvocation:testInvocation block:block]];
}
}
- (id)initWithInvocation:(NSInvocation *)anInvocation block:(id(^)(void))block
{
self = [super initWithInvocation:anInvocation];
if (!self)
return nil;
_createInstance = block;
return self;
}
- (void)setUp
{
_value = _createInstance();
}
- (void)tearDown
{
_value = nil;
}
The simplest way:
- (void)invokeTest {
[self isMemberOfClass:[AbstractClass class]] ?: [super invokeTest];
}
Copy, paste and replace AbstractClass.
I don't see a way to improve on the way you're currently doing things without digging into OCUnit itself, specifically the SenTestCase implementation of -performTest:. You'd be set if it called invoked a method to determine "Should I run this test?" The default implementation would return YES, while your version would be like your if-statement.
I'd file a Radar. The worst that could happen is your code stays the way it is now.
Related
I'm unit testing a Mac App in Xcode 5 using the new XCTest framework. Specifically, I'm trying to test that a class implements an (as yet unwritten!) - (id)initWithStream:(NSInputStream)stream method. So I have the start of my test:
- (void)testInitWithStream
{
// Should have an init method named initWithStream:
XCTAssertTrue([[MYParser alloc]
respondsToSelector:#selector(initWithStream:)],
#"'initWithStream:' not implemented.");
Which works fine; the test currently fails as expected. It's the next part, which attempts to run the as-yet-unwritten method, that's causing me grief:
// Should return nil if no valid stream is passed
XCTAssertNil([[MYParser alloc]
performSelector:#selector(initWithStream:)
withObject:nil],
#"should get nil on 'initWithStream:' with no stream.");
Xcode gives me the error "PerformSelector names a selector which retains the object." I'm using performSelector:withObject: because attempting to call the selector directly results in an error that "No visible #interface for 'MYParser' declares the selector 'initWithStream:'."
My test suite has GCC_WARN_UNDECLARED_SELECTOR turned off, but these are errors, not warnings. How can I test the init methods of objects when the test needs to cover the situation where the method may not be implemented (even as a definition) yet?
The method may not be implemented yet because I'm writing my tests first; is this right, or should I be writing my class definitions first, then my tests, then the actual implementation? (I'm looking for consensus and best-practice on this point, not just opinion.)
If you are doing test first development, you don't need to test for respondsToSelector:#selector(initWithStream:). Directly calling initWithStream: in your test suite will at first not compile. This is your failing test that you should fix before writing new code. How do you fix it? By implementing the method:
- (instancetype)initWithStream:(id)stream {
return [super init];
}
Your test now compiles, which is a little bit better than before. Running the test will fail though, because obviously the implementation doesn't do what you're testing for. Now write more code to make the test actually pass:
- (instancetype)initWithStream:(id)stream {
if (!stream) {
return nil;
}
return [super init];
}
Next, you can test for stream not being nil, which will fail so you write more code to fix it.
I was going through and replacing #synthesized(self) locks w/ this method
void _ThreadsafeInit(Class theClassToInit, void *volatile *theVariableItLivesIn, void(^InitBlock)(void))
{
//this is what super does :X
struct objc_super mySuper = {
.receiver = (id)theClassToInit,
.super_class = class_getSuperclass(theClassToInit)
};
id (*objc_superAllocTyped)(struct objc_super *, SEL, NSZone *) = (void *)&objc_msgSendSuper;
// id (*objc_superAllocTyped)(id objc_super, SEL, NSZone *) = (void *)&objc_msgSend;
do {
id temp = [(*objc_superAllocTyped)(&mySuper /*theClassToInit*/, #selector(allocWithZone:), NULL) init];//get superclass in case alloc is blocked in this class;
if(OSAtomicCompareAndSwapPtrBarrier(0x0, temp, theVariableItLivesIn)) { //atomic operation forces synchronization
if( InitBlock != NULL ) {
InitBlock(); //only the thread that succesfully set sharedInstance pointer gets here
}
break;
}
else
{
[temp release]; //any thread that fails to set sharedInstance needs to clean up after itself
}
} while (*theVariableItLivesIn == NULL);
}
which while a bit more verbose exhibits significantly better performance in non-contested cases
along with this little macro (excuse poor formatting, it's very simple). To allow the block to be declared after the initial nil check, looks to help LLVM keep the "already initialized" path extremely fast. That's the only one I care about.
#define ThreadsafeFastInit(theClassToInit, theVariableToStoreItIn, aVoidBlockToRunAfterInit) if( theVariableToStoreItIn == nil) { _ThreadsafeInitWithBlock(theClassToInit, (void *)&theVariableToStoreItIn, aVoidBlockToRunAfterInit); }
So initially implemented it using the commented out sections for objc_superAllocTyped (actually first using [theClassToInit allocWithZone:NULL], which was definitely the best approach :) ), which worked great until I realized that most of the singletons in the project had overridden allocWithZone to return the singleton method... infinite loop. So I figured using objc_msgSendSuper should sort it out quickly, but I get this error.
[51431:17c03] +[DataUtils allocWithZone:]: unrecognized selector sent to class 0x4f9584
The error doesn't seem to be related to the actual problem, as...
(lldb) po 0x4f9584
$1 = 5215620 DataUtils
(lldb) print (BOOL)[$1 respondsToSelector:#selector(allocWithZone:)]
(BOOL) $2 = YES
So I'm definitely missing something... I compared to assembly generated by a [super allocWithZone:NULL] method in an empty class... almost identical except for the functions called have different names (maybe just using different symbols, no idea, can't read it that well).
Any ideas? I can use class_getClassMethod on the superclass and call the IMP directly, but I'm trying to be reasonable in my abuse of the runtime :)
Alright, this wasn't actually that tricky once I recalled that the meta class contains all of the method information for a Class instance obtained via -[self class] or +[self] -> thanks http://www.cocoawithlove.com/2010/01/what-is-meta-class-in-objective-c.html
This error occurred because I was asking the runtime to look up the method in NSObject's set of instance methods, which obviously doesn't contain allocWithZone: . The mistake in the error log presumably originated because the receiver was a metaclass instance, and Apple has their interns implement error logs.
so while with a normal instance method call via objc_msgSendSuper, you would pass a metaclass instance as objc_super.super_class, to invoke a class method, the metaclass itself is needed (everything is one level up).
Example, and a diagram that helped me understand this - (http://www.sealiesoftware.com/blog/archive/2009/04/14/objc_explain_Classes_and_metaclasses.html)
struct objc_super mySuper;
mySuper.receiver = theClassToInit; //this is our receiver, no doubt about it
//either grab the super class and get its metaclass
mySuper.super_class = object_getClass( class_getSuperclass( theClassToInit ) );
//or grab the metaclass, and get its super class, this is the exact same object
mySuper.super_class = class_getSuperclass( object_getClass( theClassToInit ) );
Then the message can be resolved correctly. Makes perfect sense now that I started paying attention :P
Anyways, now that I found my mistake I feel like I've leveled up my Objc runtime understanding. I was also able to fix an architectural mistake made two years ago by someone I never met without having to modifying and re-test dozens of classes across 3 projects and 2 static libraries (God I love Objective-C). Replacing the #synchronized construct with a simple function call also halved the compiled code size of those methods. As a bonus, all our singleton accessors are now (more) threadsafe, because the performance cost for doing so is now negligible. Methods which naively re-fetched the singleton object multiple times (or in loops) have seen a huge speedup now that they don't have to acquire and release a mutex multiple times per invocation. All in all I'm very happy it all worked as I'd hoped.
I made a "normal" Objective-C method for this on a category of NSObject, which will work for both instance and Class objects to allow you to invoke a superclass's implementation of a message externally. Warning: This is only for fun, or unit tests, or swizzled methods, or maybe a really cool game.
#implementation NSObject (Convenience)
-(id)performSelector:(SEL)selector asClass:(Class)class
{
struct objc_super mySuper = {
.receiver = self,
.super_class = class_isMetaClass(object_getClass(self)) //check if we are an instance or Class
? object_getClass(class) //if we are a Class, we need to send our metaclass (our Class's Class)
: class //if we are an instance, we need to send our Class (which we already have)
};
id (*objc_superAllocTyped)(struct objc_super *, SEL) = (void *)&objc_msgSendSuper; //cast our pointer so the compiler can sort out the ABI
return (*objc_superAllocTyped)(&mySuper, selector);
}
so
[self performSelector:#selector(dealloc) asClass:[self superclass]];
would be equivalent to
[super dealloc];
Carry on runtime explorers! Don't let the naysayers drag you into their land of handwaving and black magik boxes, it's hard to make uncompromisingly awesome programs there*.
*Please enjoy the Objective-C runtime responsibly. Consult with your QA team for any bugs lasting more than four hours.
I'm really struggling to understand unit testing. I do understand the importance of TDD, but all the examples of unit testing I read about seem to be extremely simple and trivial. For example, testing to make sure a property is set or if memory is allocated to an array. Why? If I code out ..alloc] init], do I really need to make sure it works?
I'm new to development so I'm sure I'm missing something here, especially with all the craze surrounding TDD.
I think my main issue is I can't find any practical examples. Here is a method setReminderId that seems to be a good candidate for testing. What would a useful unit test look like to make sure this is working? (using OCUnit)
- (NSNumber *)setReminderId: (NSDictionary *)reminderData
{
NSNumber *currentReminderId = [[NSUserDefaults standardUserDefaults] objectForKey:#"currentReminderId"];
if (currentReminderId) {
// Increment the last reminderId
currentReminderId = #(currentReminderId.intValue + 1);
}
else {
// Set to 0 if it doesn't already exist
currentReminderId = #0;
}
// Update currentReminderId to model
[[NSUserDefaults standardUserDefaults] setObject:currentReminderId forKey:#"currentReminderId"];
return currentReminderId;
}
Update: I've improved on this answer in two ways: it's now a screencast, and I switched from property injection to constructor injection. See How to Get Started with Objective-C TDD
The tricky part is that the method has a dependency on an external object, NSUserDefaults. We don't want to use NSUserDefaults directly. Instead, we need to inject this dependency somehow, so that we can substitute a fake user defaults for testing.
There are a few different ways of doing this. One is by passing it in as an extra argument to the method. Another is to make it an instance variable of the class. And there are different ways of setting up this ivar. There's "constructor injection" where it's specified in the initializer arguments. Or there's "property injection." For standard objects from the iOS SDK, my preference is to make it a property, with a default value.
So let's start with a test that the property is, by default, NSUserDefaults. My toolset, by the way, is Xcode's built-in OCUnit, plus OCHamcrest for assertions and OCMockito for mock objects. There are other choices, but that's what I use.
First Test: User Defaults
For lack of a better name, the class will be named Example. The instance will be named sut for "system under test." The property will be named userDefaults. Here's a first test to establish what its default value should be, in ExampleTests.m:
#import <SenTestingKit/SenTestingKit.h>
#define HC_SHORTHAND
#import <OCHamcrestIOS/OCHamcrestIOS.h>
#interface ExampleTests : SenTestCase
#end
#implementation ExampleTests
- (void)testDefaultUserDefaultsShouldBeSet
{
Example *sut = [[Example alloc] init];
assertThat([sut userDefaults], is(instanceOf([NSUserDefaults class])));
}
#end
At this stage, this doesn't compile — which counts as the test failing. Look it over. If you can get your eyes to skip over the brackets and parentheses, the test should be pretty clear.
Let's write the simplest code we can to get that test to compile and run — and fail. Here's Example.h:
#import <Foundation/Foundation.h>
#interface Example : NSObject
#property (strong, nonatomic) NSUserDefaults *userDefaults;
#end
And the awe-inspiring Example.m:
#import "Example.h"
#implementation Example
#end
We need to add a line to the very beginning of ExampleTests.m:
#import "Example.h"
The test runs, and fails with the message, "Expected an instance of NSUserDefaults, but was nil". Exactly what we wanted. We have reached step 1 of our first test.
Step 2 is to write the simplest code we can to pass that test. How about this:
- (id)init
{
self = [super init];
if (self)
_userDefaults = [NSUserDefaults standardUserDefaults];
return self;
}
It passes! Step 2 is complete.
Step 3 is to refactor code to incorporate all changes, in both production code and test code. But there's really nothing to clean up yet. We are done with our first test. What do we have so far? The beginnings of a class that can access NSUserDefaults, but also have it overridden for testing.
Second Test: With no matching key, return 0
Now let's write a test for the method. What do we want it to do? If the user defaults has no matching key, we want it to return 0.
When first starting with mock objects, I recommend making them by hand at first, so that you get an idea of what they're for. Then start using a mock object framework. But I'm going to jump ahead and use OCMockito to make things faster. We add these lines to the ExampleTest.m:
#define MOCKITO_SHORTHAND
#import <OCMockitoIOS/OCMockitoIOS.h>
By default, an OCMockito-based mock object will return nil for any method. But I'll write extra code to make the expectation explicit by saying, "given that it's asked for objectForKey:#"currentReminderId", it will return nil." And given all that, we want the method to return the NSNumber 0. (I'm not going to pass an argument, because I don't know what it's for. And I'm going to name the method nextReminderId.)
- (void)testNextReminderIdWithNoCurrentReminderIdInUserDefaultsShouldReturnZero
{
Example *sut = [[Example alloc] init];
NSUserDefaults *mockUserDefaults = mock([NSUserDefaults class]);
[sut setUserDefaults:mockUserDefaults];
[given([mockUserDefaults objectForKey:#"currentReminderId"]) willReturn:nil];
assertThat([sut nextReminderId], is(equalTo(#0)));
}
This doesn't compile yet. Let's define the nextReminderId method in Example.h:
- (NSNumber *)nextReminderId;
And here's the first implementation in Example.m. I want the test to fail, so I'm going to return a bogus number:
- (NSNumber *)nextReminderId
{
return #-1;
}
The test fails with the message, "Expected <0>, but was <-1>". It's important that the test fail, because it's our way of testing the test, and ensuring that the code we write flips it from a failing state to a passing state. Step 1 is complete.
Step 2: Let's get the test test to pass. But remember, we want the simplest code that passes the test. It's going to look awfully silly.
- (NSNumber *)nextReminderId
{
return #0;
}
Amazing, it passes! But we're not done with this test yet. Now we come to Step 3: refactor. There's duplicate code in the tests. Let's pull sut, the system under test, into an ivar. We'll use the -setUp method to set it up, and -tearDown to clean it up (destroying it).
#interface ExampleTests : SenTestCase
{
Example *sut;
}
#end
#implementation ExampleTests
- (void)setUp
{
[super setUp];
sut = [[Example alloc] init];
}
- (void)tearDown
{
sut = nil;
[super tearDown];
}
- (void)testDefaultUserDefaultsShouldBeSet
{
assertThat([sut userDefaults], is(instanceOf([NSUserDefaults class])));
}
- (void)testNextReminderIdWithNoCurrentReminderIdInUserDefaultsShouldReturnZero
{
NSUserDefaults *mockUserDefaults = mock([NSUserDefaults class]);
[sut setUserDefaults:mockUserDefaults];
[given([mockUserDefaults objectForKey:#"currentReminderId"]) willReturn:nil];
assertThat([sut nextReminderId], is(equalTo(#0)));
}
#end
We run the tests again, to make sure they still pass, and they do. Refactoring should only be done in "green" or passing state. All tests should continue to pass, whether refactoring is done in the test code or the production code.
Third Test: With no matching key, store 0 in user defaults
Now let's test another requirement: the user defaults should be saved. We'll use the same conditions as the previous test. But we create a new test, instead of adding more assertions to the existing test. Ideally, each test should verify one thing, and have a good name to match.
- (void)testNextReminderIdWithNoCurrentReminderIdInUserDefaultsShouldSaveZeroInUserDefaults
{
// given
NSUserDefaults *mockUserDefaults = mock([NSUserDefaults class]);
[sut setUserDefaults:mockUserDefaults];
[given([mockUserDefaults objectForKey:#"currentReminderId"]) willReturn:nil];
// when
[sut nextReminderId];
// then
[verify(mockUserDefaults) setObject:#0 forKey:#"currentReminderId"];
}
The verify statement is the OCMockito way of saying, "This mock object should have been called this way one time." We run the tests and get a failure, "Expected 1 matching invocation, but received 0". Step 1 is complete.
Step 2: the simplest code that passes. Ready? Here goes:
- (NSNumber *)nextReminderId
{
[_userDefaults setObject:#0 forKey:#"currentReminderId"];
return #0;
}
"But why are you saving #0 in user defaults, instead of a variable with that value?" you ask. Because that's as far as we've tested. Hang on, we'll get there.
Step 3: refactor. Again, we have duplicate code in the tests. Let's pull out mockUserDefaults as an ivar.
#interface ExampleTests : SenTestCase
{
Example *sut;
NSUserDefaults *mockUserDefaults;
}
#end
The test code shows warnings, "Local declaration of 'mockUserDefaults' hides instance variable". Fix them to use the ivar. Then let's extract a helper method to establish the condition of the user defaults at the start of each test. Let's pull that nil out to a separate variable to help us with the refactoring:
NSNumber *current = nil;
mockUserDefaults = mock([NSUserDefaults class]);
[sut setUserDefaults:mockUserDefaults];
[given([mockUserDefaults objectForKey:#"currentReminderId"]) willReturn:current];
Now select the last 3 lines, context click, and select Refactor ▶ Extract. We'll make a new method called setUpUserDefaultsWithCurrentReminderId:
- (void)setUpUserDefaultsWithCurrentReminderId:(NSNumber *)current
{
mockUserDefaults = mock([NSUserDefaults class]);
[sut setUserDefaults:mockUserDefaults];
[given([mockUserDefaults objectForKey:#"currentReminderId"]) willReturn:current];
}
The test code that invokes this now looks like:
NSNumber *current = nil;
[self setUpUserDefaultsWithCurrentReminderId:current];
The only reason for that variable was to help us with the automated refactoring. Let's inline it away:
[self setUpUserDefaultsWithCurrentReminderId:nil];
Tests still pass. Since Xcode's automated refactoring didn't replace all instances of that code with a call to the new helper method, we need to do that ourselves. So now the tests look like this:
- (void)testNextReminderIdWithNoCurrentReminderIdInUserDefaultsShouldReturnZero
{
[self setUpUserDefaultsWithCurrentReminderId:nil];
assertThat([sut nextReminderId], is(equalTo(#0)));
}
- (void)testNextReminderIdWithNoCurrentReminderIdInUserDefaultsShouldSaveZeroInUserDefaults
{
// given
[self setUpUserDefaultsWithCurrentReminderId:nil];
// when
[sut nextReminderId];
// then
[verify(mockUserDefaults) setObject:#0 forKey:#"currentReminderId"];
}
See how we continually clean as we go? The tests have actually become easier to read!
Fourth Test: With matching key, return incremented value
Now we want to test that if the user defaults has some value, we return one greater. I'm going to copy and alter the "should return zero" test, using an arbitrary value of 3.
- (void)testNextReminderIdWithCurrentReminderIdInUserDefaultsShouldReturnOneGreater
{
[self setUpUserDefaultsWithCurrentReminderId:#3];
assertThat([sut nextReminderId], is(equalTo(#4)));
}
That fails, as desired: "Expected <4>, but was <0>".
Here's simple code to pass the test:
- (NSNumber *)nextReminderId
{
NSNumber *reminderId = [_userDefaults objectForKey:#"currentReminderId"];
if (reminderId)
reminderId = #([reminderId integerValue] + 1);
else
reminderId = #0;
[_userDefaults setObject:#0 forKey:#"currentReminderId"];
return reminderId;
}
Except for that setObject:#0, this is starting to look like your example. I don't see anything to refactor, yet. (There actually is, but I didn't notice until later. Let's keep going.)
Fifth Test: With matching key, store incremented value
Now we can establish one more test: given those same conditions, it should save the new reminder ID in user defaults. This is quickly done by copying the earlier test, altering it, and giving it a good name:
- (void)testNextReminderIdWithCurrentReminderIdInUserDefaultsShouldSaveOneGreaterInUserDefaults
{
// given
[self setUpUserDefaultsWithCurrentReminderId:#3];
// when
[sut nextReminderId];
// then
[verify(mockUserDefaults) setObject:#4 forKey:#"currentReminderId"];
}
That test fails, with "Expected 1 matching invocation, but received 0". To get it passing, of course, we simply change the setObject:#0 to setObject:reminderId. Everything passes. We're done!
Wait, we're not done. Step 3: Is there anything to refactor? When I first wrote this, I said, "Not really." But looking it over after watching Clean Code episode 3, I can hear Uncle Bob telling me, "How big should a function be? 4 lines is OK, maybe 5. 6 is… OK. 10 is way too big." That's at 7 lines. What did I miss? It must be violating the rule of functions by doing more than one thing.
Again, Uncle Bob: "The only way to be really be sure that a function does one thing is to extract 'til you drop." Those first 4 lines work together; they calculate the actual value. Let's select them, and Refactor ▶ Extract. Following Uncle Bob's scoping rule from episode 2, we'll give it a nice, long descriptive name since its scope of use is very limited. Here's what the automated refactoring gives us:
- (NSNumber *)determineNextReminderIdFromUserDefaults
{
NSNumber *reminderId = [_userDefaults objectForKey:#"currentReminderId"];
if (reminderId)
reminderId = #([reminderId integerValue] + 1);
else
reminderId = #0;
return reminderId;
}
- (NSNumber *)nextReminderId
{
NSNumber *reminderId;
reminderId = [self determineNextReminderIdFromUserDefaults];
[_userDefaults setObject:reminderId forKey:#"currentReminderId"];
return reminderId;
}
Let's clean that up to make it tighter:
- (NSNumber *)determineNextReminderIdFromUserDefaults
{
NSNumber *reminderId = [_userDefaults objectForKey:#"currentReminderId"];
if (reminderId)
return #([reminderId integerValue] + 1);
else
return #0;
}
- (NSNumber *)nextReminderId
{
NSNumber *reminderId = [self determineNextReminderIdFromUserDefaults];
[_userDefaults setObject:reminderId forKey:#"currentReminderId"];
return reminderId;
}
Now each method is really tight, and it's easy for anyone to read the 3 lines of the main method to see what it does. But I'm uncomfortable having that user defaults key spread across two methods. Let's extract that into a constant at the head of Example.m:
static NSString *const currentReminderIdKey = #"currentReminderId";
I'll use that constant wherever that key appears in the production code. But the test code continues to use the literals. This guards us from someone accidentally changing that constant key.
Conclusion
So there you have it. In five tests, I have TDD'd my way to the code you asked for. Hopefully it gives you a clearer idea of how to TDD, and why it's worth it. By following the 3-step waltz
Add one failing test
Write the simplest code that passes, even if it looks dumb
Refactor (both production code and test code)
you don't just end up at the same place. You end up with:
well-isolated code that supports dependency injection,
minimalist code that only implements what has been tested,
tests for each case (with the tests themselves verified),
squeaky-clean code with small, easy-to-read methods.
All these benefits will save more time than the time invested in TDD — and not just in the long term, but immediately.
For an example involving a full app, get the book Test-Driven iOS Development. Here's my review of the book.
Although I have seen similar questions in SO, none of the answers seems to solve my problem.
I have a NSManagedObject class generated by mogenerator with custom functions (not in the model):
#interface MyManagedClass : _MyManagedClass {
-(NSNumber*)getRandomNumber2;
-(void)function_I_want_to_test;
}
My function_I_want_to_test() depend on the result of random(), and that is something I must control during testing. Since I cannot mock random() I built a function wrapper, which by the way, is not static because I had many problems with OCMock and static class functions.
The setup of my unit test looks like:
[MagicalRecord setDefaultModelFromClass:[self class]];
[MagicalRecord setupCoreDataStackWithInMemoryStore];
Using the debugger I could verify that the model is properly loaded. Also if I do it the non magical way:
NSBundle *b = [NSBundle bundleForClass:[self class]];
model = [NSManagedObjectModel mergedModelFromBundles:#[b]];
After this point I cannot create any mock to stub my random() wrapper function
I have tried a class mock
id mock = [OCMockObject mockForClass:[MyManagedClass class]];
[[[mock stub] andReturn:#50] getRandomNumber2];
MyManagedClass *my_object = [mock MR_createEntity];
I have tried using a partial mock
MyManagedClass *my_object = [MyManagedClass MR_createEntity];
id mock2 = [OCMockObject partialMockForObject:my_object];
After last point, just creating an instance of mock2 destroys the dynamic properties of my_object, and it become useless.
I have also tried using a mock protocol with the function I want to stub, still to no avail.
The runtime exception is the normal one other people get when using with tests with Core Data objects: the properties are not recognized selectors.
However the strange thing to me is that I am not trying to stub any dynamic property, but a normal, compile time known function. Hence it seems strange to me that using OCMock renders my instances useless.
Ideally I would like something that uses OCMock/Mogenerator/Magicalrecord.
what am I doing wrong?
I recommend against trying to mock out managed objects. There's a lot of runtime craziness going on to make managed objects work. That's why I suggest going with an in memory database approach for testing. This will let you create empty instances of your entities while letting the core data stuff happen.
Since you're probably using unit tests, I suggest that in each test case where you think you need to mock out some data to instead recreate your whole stack, and set a new entity up with the state it needs to run your test. You can also make a test in memory persistent store separate from the one the default stack create method gives you, and attach this second one to your default stack. That is, make a new in memory store, initialize it with your fake/mock data entities, and attach that to your test data stack.
I hope this rambling helps a bit, but the bottom line is dont mock managed objects...really.
You could do it by moving your random number generation out of the Core Data object and into a helper class:
#implementation RandomNumberGenerator
static RandomNumberGenerator *_sharedInstance = nil;
+(RandomNumberGenerator *)sharedInstance {
if (_sharedInstance == nil) {
_sharedInstance = [[RandomNumberGenerator alloc] init];
}
return _sharedInstance;
}
+(void)setSharedInstance:(RandomNumberGenerator *)instance {
[instance retain];
[_sharedInstance release];
_sharedInstance = instance;
}
-(NSNumber *)generateRandomNumber {
return ...
}
#end
Then in your test:
-(void)testSomething {
id mockGenerator = [OCMockObject mockForClass:[RandomNumberGenerator class]];
[RandomNumberGenerator setSharedInstance:mockGenerator];
[[[mockGenerator stub] andReturn:#(4)] generateRandomNumber];
MyManagedClass *my_object = [MyManagedClass MR_createEntity];
expect(my_object.someField).to.equal(someValueBasedOnGeneratedRandomNumber);
}
I have two similar classes, MultiSlotBlock and SingleSlotBlock. They have started to share a lot of common code so I have decided to do some refactoring and pull some of the methods up to a new superclass, let's call it Block.
Now one of the methods that I pull up, simplified for the example, looks like this:
// (Block.mm)
- (void)doACommonBehaviour
{
// .. does some stuff
[self doAUniqueBehaviour];
}
The problem here is that [self doAUniqueBehaviour] is showing a warning because of course my superclass doesn't implement this method anywhere.
The two solutions I thought of don't sound great to me. One is to use a protocol (the way I am currently doing it) like so:
// (Block.mm)
- (void)doACommonBehaviour
{
// .. does some stuff
if ([self conformsToProtocol:#protocol(UniqueBehaviourProtocol)])
{
id<UniqueBehaviourProtocol> block = (id<UniqueBehaviourProtocol>)self;
[block doAUniqueBehaviour];
}
}
The other is to have a blank method body in my superclass (in this case there would be a lot) and just return doesNotRespondToSelector.
Something is tingling at the back of my mind that I should be using the Strategy Pattern, but I might be way off, and I haven't thought through how that would be implemented.
Any ideas? Thanks.
EDIT: I know for a fact that doAUniqueBehaviour will be implemented in all subclasses, it is just the implementation that will differ.
The superclass should not know about its subclasses. You should implement the
- (void)doACommonBehaviour method in every subclass and there:
- (void)doACommonBehaviour
{
[super doACommonBehaviour];
[self doAUniqueBehaviour];
}
EDIT - clarification:
If all the subclasses are going to implement -doAUniqueBehaviour then it should be implemented in the superclass (even empty) and each subclass will override it to its needs.
If subclass1 implements -doAUniqueBehaviour1, subclass2 implements -doAUniqueBehaviour2 etc then do what I propose above; eg. in subclass1:
- (void)doACommonBehaviour
{
[super doACommonBehaviour];
[self doAUniqueBehaviour1];
}
There is not such concept as abstract class in Objective-C. In order to avoid the warning, you have to provide a default implementation in your base class. Usually, this implementation will throw a doesNotRespondToSelector error at runtime:
- (id)someMethod:(SomeObject*)blah
[self doesNotRecognizeSelector:_cmd];
return nil;
}
Note: the _cmd argument is the invoked selector.
#Dimitri's suggestion will work, but instead of forcing each subclass to implement the same method, you can declare it once in Block, and just above that method (in the implementation file, not header) declare the unique method like so:
- (void) doUniqueBehaviour { }
- (void) doCommonBehaviour {
// any common code you need
[self doUniqueBehaviour];
}
This will prevent any compiler warnings, and you can override -doUniqueBehaviour in subclasses as you like. It also avoids code duplication and reduces the potential for changing the code in one subclass but not another. Plus, you don't need a separate protocol, and dynamic typing is preserved.