15
May
2012
 

Unit Testing with Core Data

by Saul Mora

Whether you subscribe to Test Driven Development (TDD) or another testing practice, when it comes automated unit testing with Core Data, things can be a little tricky. But if you keep it simple, and take things step by step, you can get up and running with unit testing using Core Data fairly quickly. We’ll explore the what, how and why of unit testing with Core Data. We’ll also be using the helper library MagicalRecord. MagicalRecord not only lets us get up and running faster, but helps to cut down on the noise in our tests.

Testing Environment

When you set up a Core Data stack using a file based store (say, using the sqlite store), that file becomes tied to the data model. That is for good reason. Core Data needs to first verify several things before it can use that data store. It must verify the entities have all the attributes the data model expects, as well as to ensure all entities are present in the store. The data and the description of that data must match exactly. However, when you are developing an app using Core Data, this schema changes fast. Adding a new attribute, or even simply changing it’s type can alter the version hashes enough so that the file version and the model version don’t match.

All this is to say that using a file based datastore for unit testing is problematic. Even if you were to set up your file based Core Data model to be auto migrating, it’s still highly likely you’ll perform a refactoring that is simply not compatible with automigrations. Another likely scenario when testing is that you’ll want to create simple test data for a single test, or suite of tests, and then delete it.

While testing with a file based Core Data store is possible, it’s fairly tricky and error prone. Generally, when you setup your test cases, you’ll want to load your sample set of data. When you’re finished, you’ll then want to delete all that data. Unit tests should fail on an error with our application code, not our test infrastructure, setup or teardown code. And besides, I know you’ve already jumped to the same conclusion I have by now, and that’s to use an In-Memory persistent store. Not only are there no file deletions to deal with, you will never have to perform a migration. Since during unit testing, you want a temporary store, the in-memory store is a perfect setup.

Note: Using an in-memory store to perform tests against a Core Data store is not an entirely new concept. Graham Lee has also previously described his process and setup for testing Core Data with an in-memory store.

Quickly Setting up Tests

Now that we’ve gone over a little background, let’s get to how we can use MagicalRecord to easily setup our Core Data tests.

As we’ve discussed previously, MagicalRecord comes with a set of helpers to build a simple Core Data stack for you. We’ll use one that’s fairly straight forward:

[MagicalRecord setupCoreDataStackWithInMemoryStore];

We’ll also want to clean up the Core Data stack after some tests, and, yes, MagicalRecord provides a handy cleanup method for you:

[MagicalRecordHelpers cleanUp];

Let’s explore when and where to incorporate these method calls into your tests.

Testing Frameworks

Up to this point, the discussion hasn’t been geared toward any particular testing framework, although the syntax is common to the SenTestingKit testing framework that is now built in (and a first class citizen in Xcode4). However, there are now several testing frameworks now that provide many structural similarities, while improving on the syntax. Let’s see how things differ across a few of the more popular testing frameworks available for Objective C.

SenTest, OCUnit, GHUnit

SenTestingKit and GHUnit offer a very similar experience from a testing perspective. Primarily, you create a new subclass of SenTestCase, or GHTestCase, and author all your tests in methods starting with the word “test“. You can also run a method before all tests in the class are performed, or before each method.

Depending on how you set up your tests, you have two options on where to put the simple MagicalRecord setup method. If you want a pristine data store for every test (useful for isolating tests), then you’ll want to put this method in the setUp method like so:

- (void)setUp;
{
	[MagicalRecord setupCoreDataStackWithInMemoryStore];
}

However, there are certain cases where you’ll need to preload a larger test data set to test against. I would recommend doing this before each and every test as it will only slow your test runs down. And making tests slower can eventually lead to you not running them at all. I recommend setting this up once per test suite:

- (void)setUpClass;
{
	[MagicalRecord setupCoreDataStackWithInMemoryStore];
}

And to clean up your stack, simply add a call to the clean up method using the appropriate post-test method, tearDown, or tearDownClass.

Kiwi, Cedar

Kiwi and Cedar are the Behavior Driven Development (BDD) style frameworks. Rather than methods, you format your tests using blocks, however there are similar mechanics when runnings tests. That is, you also have ways to setup and teardown your test Core Data stack even if the methods aren’t called setup and teardown. However, with it’s block type syntax, you can have nested setup calls to beforeAll and beforeEach, afterAll and afterEach (the BDD versions of the setup and teardown methods). You will want to place your calls to set up your test core data stack at an appropriate level within your block contexts.

describe(@"MyEntity", ^{

  beforeEach(^{
	[MagicalRecord setupCoreDataStackWithInMemoryStore];
  });

  afterEach(^{
	[MagicalRecord cleanUp];
  });

  it(@"should do something awesome", ^{ /* Put your test here! */ });

});

As you can see, setting up and cleaning up after your tests is fairly easy with MagicalRecord.

Supporting Xcode’s Unit Test Support

When using a testing framework like GHUnit, the afore mentioned steps are all that’s required to get started. However, when it comes to using Xcode’s built in unit test bundle, there is one extra step required in order to get the stack setup.

When you first need to load up the Core Data stack, you typically use the method:

[NSManagedObjectModel mergedModelsFromBundle:nil];

This method, give the parameter of nil, will look in the main bundle, which is generally the application bundle, and look for all data model files, and merge those files into a single Core Data model instance for use when instantiating an NSPersistentStoreCoordinator and it’s related NSPersistentStore. However, with the Unit Test bundle, this call does not return the correct answer, as documented by Graham Lee. The solution to this problem is simple: we need to specify the correct bundle so that the Core Data stack initialization process can continue on it’s way. To do this with MagicalRecord, we use the following setUp method for our tests:

- (void)setUp;
{
	[MagicalRecord setDefaultModelFromClass:[self class]];
	[MagicalRecord setupCoreDataStackWithInMemoryStore];
}

The setDefaultModelFromClass: method will perform the solution provided by Graham Lee, which is to load the model from the specified class, which, in turn, is the class of the currently executing test case. And now that we have something more generic, we have the ability to create a very simple boilerplate Core Data unit test case:

//CoreDataTestCase.h

@interface CoreDataTestCase : SenTestCase
@end

//CoreDataTestCase.m
#import "CoreDataTestCase.h"

@implementation CoreDataTestCase

- (void)setUp;
{
	[MagicalRecord setDefaultModelWithClass:[self class]];
	[MagicalRecord setupCoreDataStackWithInMemoryStore];
}

- (void)tearDown;
{
	[MagicalRecord cleanUp];
}

@end

This means that when you begin authoring unit tests requiring Core Data, you can simply inherit from this base test case, and you’re ready to start writing tests.

A Test Case

First of all, it goes without saying that when you’re using Core Data, you create a set of custom entity classes, which are subclasses of NSManagedObject.  And, to make your life easier, you should always use mogenerator to create your custom entities. This is where your custom logic will live. Let’s author a sample test:

@implementation SampleTestCase
- (void)setUp;
{
	[MagicalRecord setDefaultModelWithClass:[self class]];
	[MagicalRecord setupCoreDataStackWithInMemoryStore];
}

- (void)tearDown;
{
	[MagicalRecord cleanUp];
}

- (void)testSomeCalculationOnMyEntity;
{
	MyEntity *testEntity = [MyEntity MR_createEntity];
	float expectedValue = …;
	STAssert([testEntity customCalculation] == expectedValue, @"expected a good calculation");
}

@end

The MR_createEntity method will use the default context that was set up when the setupCoreDataStackWithInMemoryStore method was called earlier on in the test run. This makes for clean tests based on Core Data, but doesn’t let the Core Data syntax get in the way of expressing the intent of the test. And, when the test has completed, this sample entity will be deleted by virtue of the store being released from memory, so that the next test can have a clean slate with which to work.

Conclusion

And with that, you have an extremely simple and fast way to get a test environment setup for unit testing with Core Data. What I enjoy most about this setup is that when authoring unit tests which require Core Data, I am not focused on the details of setting up and tearing down Core Data. Rather, I’m focused on a single entity, and the things it requires in order to work properly. A sample set of unit tests which, in turn, test MagicalRecord itself are included in the MagicalRecord source on github.