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:
1 2 3 |
let order = Seeds.Orders.amy let orders = [Seeds.Orders.bob, Seeds.Orders.chris] |
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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
@testable import CleanStore import XCTest struct Seeds { struct Orders { static let billingAddress = Address(street1: "1 Infinite Loop", street2: "", city: "Cupertino", state: "CA", zip: "95014") static let shipmentAddress = Address(street1: "One Microsoft Way", street2: "", city: "Redmond", state: "WA", zip: "98052-7329") static let paymentMethod = PaymentMethod(creditCardNumber: "1234-123456-1234", expirationDate: NSDate(), cvv: "999") static let shipmentMethod = ShipmentMethod(speed: .Standard) static let amy = Order(firstName: "Amy", lastName: "Apple", phone: "111-111-1111", email: "amy.apple@clean-swift.com", billingAddress: billingAddress, paymentMethod: paymentMethod, shipmentAddress: shipmentAddress, shipmentMethod: shipmentMethod, id: "aaa111", date: NSDate(), total: NSDecimalNumber(string: "1.11")) static let bob = Order(firstName: "Bob", lastName: "Battery", phone: "222-222-2222", email: "bob.battery@clean-swift.com", billingAddress: billingAddress, paymentMethod: paymentMethod, shipmentAddress: shipmentAddress, shipmentMethod: shipmentMethod, id: "bbb222", date: NSDate(), total: NSDecimalNumber(string: "2.22")) static let chris = Order(firstName: "Chris", lastName: "Camera", phone: "333-333-3333", email: "chris.camera@clean-swift.com", billingAddress: billingAddress, paymentMethod: paymentMethod, shipmentAddress: shipmentAddress, shipmentMethod: shipmentMethod, id: "ccc333", date: NSDate(), total: NSDecimalNumber(string: "3.33")) } } |
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.
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 ?
Sorry for the messy code, I don’t know why it can’t show the code correctly.
Task is my main model, like the order model in your sample app, and dispayedTask is a shared model.
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.
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!
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.
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.
Prob better to use enum for namespacing since you can’t call an init where on a struct you can
Hi Kaden,
This post is from some time ago. The latest templates are using enums for namespacing.