How can I unit-test init methods in Objective-C? - objective-c

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.

Related

OCMock failures "Another mock is already associated with object" with XCTest in XCode 7

I just upgraded to XCode 7 recently, forcing me to upgrade OCMock so that I have support for x64 architectures. Apparently a change was made in OCMock which does not allow for a previously mocked object to be remocked-- in other words, I had a helper method that did something like this:
-(MyObject *)getObject {
Factory *factory = [self.dependencyInjector getInstance:factory];
id mockFactory = [OCMockObject partialMockForObject:factory];
[[[mockFactory stub] andReturn:#"important-value"] thing];
return [[MYObject alloc] initWithFactory:mockFactory];
}
This worked fine previously, but apparently there was a change to OCMockObject to not allow a re-mocking of an already mocked object. Since the factory object returned by the injector is effectively a singleton, subsequent calls to the getObject method is calling partialMockForObject: on it multiple times and this now throws an exception "Another mock is already associated with object".
Is there any way to make OCMock not throw an error? I tried calling stopMocking on the object prior to mocking it, but that does not fix this issue. The only way around it was to do something like:
-(MyObject *)getObject {
if (!self.mockFactory) {
Factory *factory = [self.dependencyInjector getInstance:factory];
id mockFactory = [OCMockObject partialMockForObject:factory];
[[[mockFactory stub] andReturn:#"important-value"] thing];
self.mockFactory = mockFactory;
}
return [[MYObject alloc] initWithFactory:self.mockFactory];
}
which is really annoying to have to do...
Looking at the code as it is today, the implementation of stopMocking explicitly resets the associated object (https://github.com/erikdoe/ocmock/blob/dd5599695dcc50afe4d6bdff509ed3cbe389c667/Source/OCMock/OCPartialMockObject.m#L80). I'm at a loss how calling stopMocking doesn't solve the problem. Can you build a debug version of OCMock and set a breakpoint at the line highlighted above and see whether it's called?

How to partially mock an object inside legacy code with OCMock?

I would like to accomplish what also is described here, i.e create mocks inside legacy code. However I require partial instead of nice or strict mocks.
For example, consider leaderboards that behave exactly like GKLeaderbaord except for implementing a stubbed version of loadScoresWithCompletionHandler:.
I've tried this code inside an XCTestCase but it currently fails at runtime in the indicated line: OCMInvocationMatcher raises an EXC_BAD_ACCESS error. Perhaps there is some infinite recursion going on.
id leaderboardMock = OCMClassMock(GKLeaderboard.class);
OCMStub([leaderboardMock alloc])
.andReturn(OCMPartialMock([GKLeaderboard alloc]));
OCMStub([leaderboardMock loadScoresWithCompletionHandler: [OCMArg any]])
.andDo(^(NSInvocation *invocation) { /* ... */ });
// these parts normally nested inside legacy code
GKLeaderboard *leaderboard = /* raises EXC_BAD_ACCESS */
[[GKLeaderboard alloc] initWithPlayers: #[ GKLocalPlayer.localPlayer ]];
leaderboard.identifier = #"Test";
[leaderboard loadScoresWithCompletionHandler: nil /* ... */ ];
What am I doing wrong and is this even possible for partial mockups?
UPDATE I can by now see how the indicated line might (quite obviously) cause an infinite recursion, but don't yet know how to avoid (or break) it.
UPDATE I've also had no success with an attempt of bringing in an dedicated class with OCMStub([leaderboardMock alloc]).andReturn([LeaderboardMock alloc]) (nor with OCMStub([leaderboardMock initWithPlayers: [OCMArg any]]).andReturn([[LeaderboardMock alloc] initWithPlayers:nil])). Perhaps OCMock does its magic at the level of init (the documentation says: "it is not possible to stub the init method, because that is implemented by the mock itself") hence such an attempt the level of alloc (or initWithPlayers:) cannot have its desired effect.
Not sure I follow what you are trying to do. It seems like a misunderstanding. Does the following not work for you?
GKLeaderboard *leaderboard = [[GKLeaderboard alloc] initWithPlayers: ... ];
id leaderboardMock = OCMPartialMock(leaderboard);
OCMStub([leaderboarMock loadScoresWithCompletionHandler: ...]);
You can use the normal object without restrictions. You can use the partial mock created for the object to manipulate the actual instance in leaderboard. That's the beauty of partial mocks.
UPDATE: If the object creation is not under your control, you can try the following:
GKLeaderboard *leaderboard = [[GKLeaderboard alloc] initWithPlayers: ... ];
id leaderboardMock = OCMPartialMock(leaderboard);
OCMStub([leaderboardMock alloc]).andReturn(leaderboardMock);
OCMStub([leaderboardMock initWithPlayers:[OCMArg any]).andReturn(leaderboard);
OCMStub([leaderboarMock loadScoresWithCompletionHandler: ...]);
I have by now concluded that method swizzling would be a possible choice.
A replacement method could e.g. generate a partial mockup from within the context of legacy code and hence introduce a partial mock in that context without requiring changes to legacy APIs.
you should not use following line, it will mock your entire class and none of real object will get called.
OCMClassMock(GKLeaderboard.class)

Objective-C iOS callback function between classes

I'm new to iPhone application development, but I seem to have somewhat managed so far (learning as I go).. however, I've run in to an issue I can't figure out. Here's the scenario:
I have an extension class called CoreAPI which I have my network functions inside. I have a function called "Login" inside CoreAPI which makes a request to a server, gets the 0 or 1 response and returns true or false. I had this working perfectly with Synchronous requests, but now I need to make everything asynchronous.
So, below is what I'm trying to do.. this is not working with the error along the lines of "Object of type ID has no method loginCallback"
loginViewController.m
- (void) login {
CoreAPI APIObject = [[CoreAPI alloc] init];
[APIObject login:self];
}
- (void) loginCallback {
NSLog(#"It called back.");
}
CoreAPI.m
- (void)login:(id)sender {
[sender loginCallback];
}
Thanks, and please let me know if I have missed any details needed to help me.
I'm seeing a couple problems. First, I'm guessing you don't provide visibility of the loginCallback function to CoreAPI.m. You should be able to send any message to an id type, so long as it's defined for a known type.
However, I'd recommend using a completion block in this case. Here's what that could look like:
(in CoreAPI.h)
- (void)login:(void (^)(void))completed;
(in CoreAPI.m)
- (void)login:(void (^)(void))completed {
// Login code
if (completed) completed();
}
Calling it would look like:
CoreAPI APIObject = [[CoreAPI alloc] init];
[APIObject login:^{
NSLog(#"It called back.");
}];
They have really goofy syntax, but blocks are really nice for this sort of thing. Hope this helped! Let me know if I didn't explain something clearly.
this should do the trick for you:
first, import loginViewController header inside CoreApi.m
#import "loginViewController.h"
Then, change login method to this:
- (void)login:(id)sender {
[(loginViewController*)sender loginCallback];
}
Or this:
- (void)login:(loginViewController*)sender {
[sender loginCallback];
}
Explanation: notice that your login method is receiving by parameter one object of type id . In objective C, id type means a reference to any Objective-C of unknow class. So, inside your login method, the compiler doesn't know that the sender is a instance of your loginViewController class, so it won't recognize loginViewController's methods.
To informations about this, please read: Objective-C: difference between id and void *
Notice that I only focused in remove your actual error. You should have to do more things in order to accomplish your code to run asynchronous.
In order to perform a better callback, please look for delegates or blocks (like in oltman's answer).
To run things in background, look for CDG : http://developer.apple.com/library/ios/#DOCUMENTATION/General/Conceptual/ConcurrencyProgrammingGuide/OperationQueues/OperationQueues.html

Unit Testing Example with OCUnit

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.

How to implement or emulate an "abstract" OCUnit test class?

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.