Understanding testdatagen functions
We are deprecating the testdatagen functions. Please check if the newer factory function is available to build your test data model → Factory Test Data
Why we use testdatagen functions
When we unit test our code, we need a way to insert records in the database to test with. We need sample records to manipulate, update, delete, etc.
We could create them by using Pop to create in the DB directly. However, our records have a lot of interdependencies and constraints. Here’s a subset of the constraints you’d have to understand, and handle, to create a payment request:
- Payment requests must link to existent service items.
- Payment requests must link to an existent MTO record.
- The MTO must link to an existent Orders record.
- Payment requests must use a sequence_id > 0
- Payment requests must have a PaymentRequestNumber the list goes on…
So to test something as simple as a function to approve a Payment Request, you’d be writing a lot of test code just to create that Payment Request.
That’s where testdatagen.Make
functions come in handy. They efficiently create records with default values, while handling all the dependencies, and even allowing you to override default values.
It's important to note that they create records in the db by default, so if you just want a model that doesn't need to exist in the db, use the stub feature.
How to use a testdatagen function
To use a testdatagen function, you import the testdatagen package from github.com/transcom/mymove/pkg/testdatagen
and use the Make functions.
Let's assume we're creating an MTOShipment.
Defaults provided
The testdatagen function should provide defaults for value in the record, so you don't need to provide these. The following should create an MTOShipment in the db and return a model with the data.
mtoShipment := testdatagen.MakeDefaultMTOShipment(suite.DB())
Assertions
However if you want to change a field in the record, you can provide an assertion
to do so.
An assertion says, create the default record, but overwrite this field.
mtoShipment := testdatagen.MakeMTOShipment(suite.DB(),
testdatagen.Assertions{
MTOShipment: models.MTOShipment{
CustomerRemarks: swag.String("my special remarks"),
},
})
What this says is: create a default MTOShipment but overwrite the CustomerRemarks
field.
CAVEAT: Overriding does not work for false
boolean values. If some attribute is true
by default, setting it to false
via Assertions won't work. This is a known issue in the mergo package we use for merging the structs. To work around this, you can either just avoid making the default value true
in the base function.
Or you can use a helper function like this:
func setDependentsAuthorized(assertionDependentsAuthorized *bool) *bool {
dependentsAuthorized := swag.Bool(true)
if assertionDependentsAuthorized != nil {
dependentsAuthorized = assertionDependentsAuthorized
}
return dependentsAuthorized
}
entitlement := models.Entitlement{
DependentsAuthorized: setDependentsAuthorized(assertions.Entitlement.DependentsAuthorized),
...
}
Dependent objects
In addition to creating the record you requested, the MTOShipment
, the Make function will also create every nested record it needs, by calling those objects' Make functions.
Pretty awesome not to have to create all of these records!
MakeMTOShipment
├── MakeMove
│ ├── MakeOrder
│ │ ├── MakeExtendedServiceMember
│ │ ├── MakeDocument
│ │ └── MakeUserUpload
│ └── MakeContractor
└── MakeAddress
So what if you want to overwrite fields in those nested objects? You can, in fact, pass in assertions for those records.
Assertions on dependent records
Dependent records are things like Move
, that MTOShipment
nests in its model.
But the assertions work differently. You want to flatten the assertions, so that in the testdatagen.Assertions
object, they are all at the top level.
Notice inside the assertions, that MTOShipment
and Move
are at the same level.
mtoShipment := testdatagen.MakeMTOShipment(suite.DB(),
testdatagen.Assertions{
MTOShipment: models.MTOShipment{
CustomerRemarks: swag.String("my special remarks"),
},
Move: models.Move{
ReferenceID: "1234567",
},
})
This ensures that when the nested Make functions get called, they can find all your assertions and apply them correctly.
Reusing objects
In the above examples, the Make functions are always creating new records with a new uuid, so a new MTOShipment
, Move
, Order
etc. At times, you may want to provide an record you have already created.
mto := testdatagen.MakeAvailableMove(suite.DB())
.
.
.
mtoShipment1 := testdatagen.MakeMTOShipment(suite.DB(),
testdatagen.Assertions{
Move: mto,
})
This will connect that shipment to the previously created MTO.
Stubbing objects
In many cases, you should not need to use the DB to test logic. To keep using the handy testdatagen functions but without saving them to the DB, you can pass in the top-level Stub: true
assertion, like this:
officeUser := testdatagen.MakeTOOOfficeUser(suite.DB(), testdatagen.Assertions{
Stub: true,
})
For more details, read about How to write fast tests.