Permalink
Browse files

Shared example groups now use a shared context dictionary, stored on …

…the SpecHelper object; this should avoid the need to instantiate (and leak) dictionaries all over test code, as well as difficult to find bugs related to the capture of context dictionaries in the shared example closures.
  • Loading branch information...
Adam Milligan
Adam Milligan committed Oct 4, 2010
1 parent f044517 commit f70dd61fff65ae7c47dcc716d0432663eee12ea3
@@ -57,7 +57,7 @@
AEEE21C211DC290400029872 /* SpecHelperSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = AEEE1FF011DC27B800029872 /* SpecHelperSpec.m */; };
AEEE21C311DC290400029872 /* SpecSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = AEEE1FF111DC27B800029872 /* SpecSpec.m */; };
AEEE21C411DC290400029872 /* SpecSpec2.m in Sources */ = {isa = PBXBuildFile; fileRef = AEEE1FF211DC27B800029872 /* SpecSpec2.m */; };
- AEEE220311DC29AC00029872 /* Cedar in Frameworks */ = {isa = PBXBuildFile; fileRef = AEEE1FB611DC271300029872 /* Cedar */; };
+ AEEE220311DC29AC00029872 /* Cedar.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AEEE1FB611DC271300029872 /* Cedar.framework */; };
AEEE223111DC2B6500029872 /* CDRDefaultReporter.m in Sources */ = {isa = PBXBuildFile; fileRef = AEEE1FC411DC27B800029872 /* CDRDefaultReporter.m */; };
AEEE223211DC2B6500029872 /* CDRExample.m in Sources */ = {isa = PBXBuildFile; fileRef = AEEE1FC511DC27B800029872 /* CDRExample.m */; };
AEEE223311DC2B6500029872 /* CDRExampleBase.m in Sources */ = {isa = PBXBuildFile; fileRef = AEEE1FC611DC27B800029872 /* CDRExampleBase.m */; };
@@ -220,7 +220,7 @@
AE135D1611DEA69A00A922D4 /* OCHamcrest.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = OCHamcrest.xcodeproj; path = OCHamcrest/Source/OCHamcrest.xcodeproj; sourceTree = "<group>"; };
AE135D2611DEA6A900A922D4 /* OCMock.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = OCMock.xcodeproj; path = OCMock/Source/OCMock.xcodeproj; sourceTree = "<group>"; };
AE135E8311DEB4E400A922D4 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = folder; name = Foundation.framework; sourceTree = SDKROOT; };
- AEEE1FB611DC271300029872 /* Cedar */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = wrapper.framework; path = Cedar; sourceTree = BUILT_PRODUCTS_DIR; };
+ AEEE1FB611DC271300029872 /* Cedar.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Cedar.framework; sourceTree = BUILT_PRODUCTS_DIR; };
AEEE1FB811DC271300029872 /* Cedar-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Cedar-Info.plist"; sourceTree = "<group>"; };
AEEE1FC411DC27B800029872 /* CDRDefaultReporter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CDRDefaultReporter.m; sourceTree = "<group>"; };
AEEE1FC511DC27B800029872 /* CDRExample.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CDRExample.m; sourceTree = "<group>"; };
@@ -263,7 +263,7 @@
AEEE1FF011DC27B800029872 /* SpecHelperSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SpecHelperSpec.m; sourceTree = "<group>"; };
AEEE1FF111DC27B800029872 /* SpecSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SpecSpec.m; sourceTree = "<group>"; };
AEEE1FF211DC27B800029872 /* SpecSpec2.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SpecSpec2.m; sourceTree = "<group>"; };
- AEEE218611DC28E200029872 /* Specs */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = "compiled.mach-o.executable"; path = Specs; sourceTree = BUILT_PRODUCTS_DIR; };
+ AEEE218611DC28E200029872 /* Specs */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = Specs; sourceTree = BUILT_PRODUCTS_DIR; };
AEEE222111DC2A1400029872 /* MACROS */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = MACROS; sourceTree = "<group>"; };
AEEE222211DC2A1400029872 /* Rakefile */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = Rakefile; sourceTree = "<group>"; };
AEEE222311DC2A1400029872 /* README.markdown */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = README.markdown; sourceTree = "<group>"; };
@@ -288,7 +288,7 @@
files = (
AE135D4011DEA6F400A922D4 /* OCMock.framework in Frameworks */,
AE135D4111DEA6F400A922D4 /* OCHamcrest.framework in Frameworks */,
- AEEE220311DC29AC00029872 /* Cedar in Frameworks */,
+ AEEE220311DC29AC00029872 /* Cedar.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -360,7 +360,7 @@
AEEE1FB711DC271300029872 /* Products */ = {
isa = PBXGroup;
children = (
- AEEE1FB611DC271300029872 /* Cedar */,
+ AEEE1FB611DC271300029872 /* Cedar.framework */,
AEEE218611DC28E200029872 /* Specs */,
AEEE222911DC2B0600029872 /* libCedar-StaticLib.a */,
AEEE227611DC2CF900029872 /* iPhoneSpecs.app */,
@@ -519,7 +519,7 @@
);
name = Cedar;
productName = Cedar;
- productReference = AEEE1FB611DC271300029872 /* Cedar */;
+ productReference = AEEE1FB611DC271300029872 /* Cedar.framework */;
productType = "com.apple.product-type.framework";
};
AEEE218511DC28E200029872 /* Specs */ = {
View
@@ -124,14 +124,12 @@ Declaring shared examples inline with your specs is the simplest:
});
});
- NSDictionary *context = [NSDictionary dictionary];
-
describe(@"Something that shares behavior", ^{
- itShouldBehaveLike(@"a similarly-behaving thing", context);
+ itShouldBehaveLike(@"a similarly-behaving thing");
});
describe(@"Something else that shares behavior", ^{
- itShouldBehaveLike(@"a similarly-behaving thing", context);
+ itShouldBehaveLike(@"a similarly-behaving thing");
});
SPEC_END
@@ -151,27 +149,34 @@ specifically for declaring shared example groups:
SHARED_EXAMPLE_GROUPS_END
The context dictionary allows you to pass example-specific state into the shared
-example group. It is important that you define the dictionary object you pass
-in at spec definition time, rather than spec run time. To put it another way,
-instantiate your context dictionary in a describe block, not in a beforeEach
-block.
-
-### Long-winded explanation:
-The reason for defining your context dictionary at example definition time has
-to do with the way the example blocks capture state. If the context dictionary
-is nil at the point that this function executes:
-
- itShouldBehaveLike(@"a similarly-behaving thing", context);
-
-the framework will pass that nil to the shared example group block, and all of
-the example blocks inside the shared example group will capture that nil value.
-Since the closure captures the parameter, not the original context variable,
-changing the context variable at spec run time will not affect the captured
-value. However, as long as the context dictionary is defined at the time you
-call the itShouldBehaveLike() function, the contained blocks will capture the
-pointer value of the dictionary parameter. You can then add values to the
-dictionary at spec run time, and the shared examples will have access to those
-values via the valid dictionary pointer.
+example group. You can populate the context dictionary available on the SpecHelper
+object, and each shared example group will receive it:
+
+ sharedExamplesFor(@"a red thing", ^(NSDictionary *context) {
+ it(@"should be red", ^{
+ Thing *thing = [context objectForKey:@"thing"];
+ assertThat(thing.color, equalTo(red));
+ });
+ });
+
+ describe(@"A fire truck", ^{
+ beforeEach(^{
+ [[SpecHelper specHelper].sharedExampleContext setObject:[FireTruck fireTruck] forKey:@"thing"];
+ });
+ itShouldBehaveLike(@"a red thing");
+ });
+
+ describe(@"An apple", ^{
+ beforeEach(^{
+ [[SpecHelper specHelper].sharedExampleContext setObject:[Apple apple] forKey:@"thing"];
+ });
+ itShouldBehaveLike(@"a red thing");
+ });
+
+Previously, you needed to instantiate and pass in your own dictionary, but this
+led to confusion and unavoidable memory leaks. You should change any code that
+uses a local context dictionary to use the global shared example context
+dictionary.
## Mocks and stubs
@@ -6,28 +6,21 @@
extern CDRSpec *currentSpec;
@interface SpecHelper (CDRSharedExampleGroupPoolFriend)
-- (NSMutableDictionary *)sharedExampleGroups;
+@property (nonatomic, retain, readonly) NSMutableDictionary *sharedExampleGroups;
@end
-@implementation SpecHelper (CDRSharedExampleGroupPoolFriend)
-- (NSMutableDictionary *)sharedExampleGroups {
- return sharedExampleGroups_;
-}
-@end
-
-
void sharedExamplesFor(NSString *groupName, CDRSharedExampleGroupBlock block) {
[[[SpecHelper specHelper] sharedExampleGroups] setObject:[[block copy] autorelease] forKey:groupName];
}
-void itShouldBehaveLike(NSString *groupName, NSDictionary *context) {
+void itShouldBehaveLike(NSString *groupName) {
CDRSharedExampleGroupBlock sharedExampleGroupBlock = [[[SpecHelper specHelper] sharedExampleGroups] objectForKey:groupName];
CDRExampleGroup *parentGroup = currentSpec.currentGroup;
currentSpec.currentGroup = [CDRExampleGroup groupWithText:[NSString stringWithFormat:@"(as %@)", groupName]];
[parentGroup add:currentSpec.currentGroup];
- sharedExampleGroupBlock(context);
+ sharedExampleGroupBlock([SpecHelper specHelper].sharedExampleContext);
currentSpec.currentGroup = parentGroup;
}
@@ -3,13 +3,13 @@
@protocol CDRSharedExampleGroupPool
@end
-typedef void (^CDRSharedExampleGroupBlock)(NSDictionary *context);
+typedef void (^CDRSharedExampleGroupBlock)(NSDictionary *);
#ifdef __cplusplus
extern "C" {
#endif
void sharedExamplesFor(NSString *, CDRSharedExampleGroupBlock);
-void itShouldBehaveLike(NSString *, NSDictionary *);
+void itShouldBehaveLike(NSString *);
#ifdef __cplusplus
}
#endif
@@ -3,10 +3,12 @@
#import "CDRExampleParent.h"
@interface SpecHelper : NSObject <CDRExampleParent> {
- NSMutableDictionary *sharedExampleGroups_;
+ NSMutableDictionary *sharedExampleGroups_, *sharedExampleContext_;
}
-+ (id)specHelper;
+@property (nonatomic, retain, readonly) NSMutableDictionary *sharedExampleContext;
+
++ (SpecHelper *)specHelper;
- (void)beforeEach;
- (void)afterEach;
View
@@ -1,9 +1,15 @@
#import "SpecHelper.h"
+@interface SpecHelper ()
+@property (nonatomic, retain, readwrite) NSMutableDictionary *sharedExampleGroups, *sharedExampleContext;
+@end
+
static SpecHelper *specHelper__;
@implementation SpecHelper
+@synthesize sharedExampleGroups = sharedExampleGroups_, sharedExampleContext = sharedExampleContext_;
+
+ (id)specHelper {
if (!specHelper__) {
specHelper__ = [[SpecHelper alloc] init];
@@ -13,13 +19,15 @@ + (id)specHelper {
- (id)init {
if (self = [super init]) {
- sharedExampleGroups_ = [[NSMutableDictionary alloc] init];
+ self.sharedExampleGroups = [NSMutableDictionary dictionary];
+ self.sharedExampleContext = [NSMutableDictionary dictionary];
}
return self;
}
- (void)dealloc {
- [sharedExampleGroups_ release];
+ self.sharedExampleGroups = nil;
+ self.sharedExampleContext = nil;
[super dealloc];
}
@@ -31,6 +39,7 @@ - (void)afterEach {
#pragma mark CDRExampleParent
- (void)setUp {
+ [self.sharedExampleContext removeAllObjects];
[self beforeEach];
}
View
@@ -122,12 +122,19 @@ void expectFailure(CDRSpecBlock block) {
});
describe(@"that contains a beforeEach in a shared example group", ^{
- itShouldBehaveLike(@"a describe context that contains a beforeEach in a shared example group", [NSDictionary dictionary]);
+ itShouldBehaveLike(@"a describe context that contains a beforeEach in a shared example group");
it(@"should not run the shared beforeEach before specs outside the shared example group", ^{
assertThat(globalValue__, nilValue());
});
});
+
+ describe(@"that sets a value in the global shared example context", ^{
+ beforeEach(^{
+ globalValue__ = @"something";
+ [[SpecHelper specHelper].sharedExampleContext setObject:globalValue__ forKey:@"value"];
+ });
+ });
});
SPEC_END
@@ -137,6 +144,7 @@ void expectFailure(CDRSpecBlock block) {
sharedExamplesFor(@"a describe context that contains a beforeEach in a shared example group", ^(NSDictionary *context) {
beforeEach(^{
+ assertThatInt([[SpecHelper specHelper].sharedExampleContext count], equalToInt(0));
globalValue__ = [NSString string];
});
@@ -145,4 +153,10 @@ void expectFailure(CDRSpecBlock block) {
});
});
+sharedExamplesFor(@"a shared example group that receives a value in the context", ^(NSDictionary *context) {
+ it(@"should receive the values set in the global shared example context", ^{
+ assertThat([context objectForKey:@"value"], equalTo(globalValue__));
+ });
+});
+
SHARED_EXAMPLE_GROUPS_END

0 comments on commit f70dd61

Please sign in to comment.