Seed data for your unit tests

As I added new scenes and wrote new unit tests for Clean Swift Xcode templates v2, creating test data manually becomes very tedious. There’s got to be a better way.

In the Rails-land, seed data for testing purpose is built right in to the framework. You can create all your test fixtures as YAML files in the fixtures folder. So you would create a file named fixtures/orders.yml and put your test orders there.

It’s time to have better support for test data in iOS.

A backward approach

When you want to create a test order, what do you want the syntax to look like? I think something like this will be nice:

Seeds.Orders.amy creates a test order for Amy (i.e. the order’s first name is Amy).

Very straightforward. So how can you design your test data so you can invoke it like that?

A simple solution

An order is modeled as a struct, so it makes sense to use struct for test orders too. Let’s also make it clear that test orders are, well, test orders. So I nest the Orders struct inside the Seeds struct.

Since I don’t want to create an instance of these structs (i.e. no extra ()), I use the static keyword.

Do these test orders need to change? No. Hence let instead of var.

Then just create an Order as usual.

I also don’t really care about the billing address, shipment address, payment method and shipment method. At least not yet at this point of the development phase. I can use static constants for these too.

So here you go. A simple test fixture to really DRY up your unit tests.

Raymond
I've been developing in iOS since the iPhone debuted, jumped on Swift when it was announced. Writing well-tested apps with a clean architecture has been my goal.

9 Comments

  1. I also found this problem when I was writing tests. My approach is create “static var testObject” in every request, response and viewModel and order models.

    For example:

    And in the scenes models:

    I think this way when I need to modify my model, I only need to update those testObjects. I use those testObject computed properties as kind of API with my tests, I think the relation between models and tests will be more loose coupling as well.

    Thanks again for your VIP cycle, I am enjoying it.
    Any suggestions ?

      1. Task is my main model, like the order model in your sample app, and dispayedTask is a shared model.

    1. Hi Andres,

      No worries. I fixed the formatting for you. Your approach certainly ‘works.’ But I wouldn’t do that because it would unnecessarily contaminate the models.

      Think about it this way. Your have an app target and a test target. Your test target depends on the app target. That’s fine. What would you test without an app? But should your app really need to know about test data in the app target?

      When you make a change in your models, you also need to fix the test data so your app can run. But you really shouldn’t have to when you just want to run your app, not the tests. You should only need to fix the test data when you run your tests.

      Lastly, sprinkling your models with a lot of test data just makes it really hard to read.

      So I would keep models and test data separate.

      1. You are right, thanks!

        By the way, recently I found that I’ve instantiated CoreDataStore in every Configurator of my scenes:

        ….
        taskWorker.dataStoreProtocol = CoreDataStore()

        This is wrong because instead of multiple instances, I only need one instance of the CoreDataStore. So, I apply singleton pattern to it, the line now look like this:

        taskWorker.dataStoreProtocol = CoreDataStore.sharedInstance

        Do you think this is OK? I Just want to hear your opinion. Thanks in advance!

        1. Hi Andres,

          Instead of specifying a Core Data store at the interactor level, you can absolutely make a custom initializer for your data store worker that takes an argument to specify the type of the data store. You can then make it a default argument to use Core Data.

  2. I would just use randomizer for different values with ability to seed it, so ‘orange’ tests are easy to reproduce next time you need it and you don’t need to come up with test data all the time.

    Would be also great to have something like cassettes in Rails. Record data from some real-liofe example (dev/stage server) into some yml file and seed entities from it in tests.

  3. Prob better to use enum for namespacing since you can’t call an init where on a struct you can

Leave a Comment

Your email address will not be published. Required fields are marked *