If you follow me on twitter @paytonrules you’ve probably seen me griping at various times about writing a testing framework for Objective-C.
I’m currently writing my first expectations, and bootstrapping a testing framework continues to be an interesting problem, which I’ll probably write more about later.
I find I continually have to go backwards on the tests to clean them, because as I implement more features I start using them in earlier tests. Meanwhile I want to clean the test I’ve recently written, often without the benefits of features I don’t have yet.
The code below are the tests for my first expectations. Currently I see a lot of duplication, and I’m curious how you might go about cleaning it with the framework in it’s current state. Keep in mind these rules:
- I don’t have a before or after feature yet, and since some developers swear that using before and after is a bad practice, you can’t use that.
-
I don’t have expectations that take primitive types, which is why you see
expect(obj)
in some places, but a crude check then FAIL in others. I will, but don’t yet. - By using this DSL you don’t have access to a class to put variables on like you would in an XUnit style test. I have no idea of OCMock will work for mocking objects in my framework yet.
Given all those constraints—clean this code!
#import "OCDSpec/OCDSpec.h"
#import "OCDSpec/OCDSpecExpectation.h"
#import "OCDSpec/OCDSpecFail.h"
#import "Specs/Mocks/MockObjectWithEquals.h"
CONTEXT(OCDSpecExpectation)
{
describe(@"The Expecation",
^{
MockObjectWithEquals *actualObject = [[[MockObjectWithEquals alloc] init] autorelease];
MockObjectWithEquals *expectedObject = [[[MockObjectWithEquals alloc] init] autorelease];
OCDSpecExpectation *expectation = [[[OCDSpecExpectation alloc] initWithObject:actualObject inFile:@"" atLineNumber:0] autorelease];
[expectation toBeEqualTo:expectedObject];
[expect(actualObject) toBeEqualTo:expectedObject];
}),
it(@"throws a failure when the objects aren't equal with an explanatory reason if the two objects are not equal to each other",
^{
MockObjectWithEquals *actualObject = [[[MockObjectWithEquals alloc] initAsNotEqual] autorelease];
MockObjectWithEquals *expectedObject = [[[MockObjectWithEquals alloc] init] autorelease];
OCDSpecExpectation *expectation = [[[OCDSpecExpectation alloc] initWithObject:actualObject inFile:@"" atLineNumber:0] autorelease];
@try
{
[expectation toBeEqualTo:expectedObject];
FAIL(@"Code did not throw a failure exception");
}
@catch (NSException *exception)
{
NSString *expectedReason = [NSString stringWithFormat:@"%@ was expected to be equal to %@, and isn't", actualObject, expectedObject];
[expect([exception reason]) toBeEqualTo:expectedReason];
}
}),
it(@"throws a failure with the line and file passed in - i.e. uses OCDSpecFail",
^{
MockObjectWithEquals *actualObject = [[[MockObjectWithEquals alloc] initAsNotEqual] autorelease];
MockObjectWithEquals *expectedObject = [[[MockObjectWithEquals alloc] init] autorelease];
OCDSpecExpectation *expectation = [[[OCDSpecExpectation alloc] initWithObject:actualObject inFile:@"FILENAME" atLineNumber:120] autorelease];
@try
{
[expectation toBeEqualTo:expectedObject];
FAIL(@"Code did not throw a failure exception");
}
@catch (NSException *exception)
{
[expect([[exception userInfo] objectForKey:@"file"]) toBeEqualTo:@"FILENAME"];
if (![[[exception userInfo] objectForKey:@"line"] isEqual:[NSNumber numberWithLong:120]])
{
FAIL(@"Should have had line number 120, didn't");
}
}
}),
it(@"Is created helpfully by the expect macro",
^{
NSObject *innerObject;
OCDSpecExpectation *expectation = expect(innerObject);
if (expectation.line != __LINE__ -2)
FAIL(@"Line Number is wrong");
[expect(expectation.file) toBeEqualTo:[NSString stringWithUTF8String:__FILE__]];
}),
nil);
}