FakeIt Series (Part 3 of 5): Lean Models Through Definitions
Learn how definitions allow you to streamline your FakeIt models by creating reusable sets of code for smaller more efficient models.
In our previous post on shared data and dependencies, we saw how to create multi-model dependencies with FakeIt. Today, we are going to look at how we can create similar but smaller models by leveraging definitions.
Definitions are a way of creating reusable set(s) within your model. It allows you to define a set of properties/values one time, referencing them multiple times throughout your model. Definitions in a FakeIt model are very similar to how definitions are used in the Swagger/Open API Specification.
Users Model
We will start with our users.yaml
model that we defined in our first post.
name: Users
type: object
key: _id
properties:
_id:
type: string
description: The document id built by the prefix "user_" and the users id
data:
post_build: `user_${this.user_id}`
doc_type:
type: string
description: The document type
data:
value: user
user_id:
type: integer
description: An auto-incrementing number
data:
build: document_index
first_name:
type: string
description: The users first name
data:
build: faker.name.firstName()
last_name:
type: string
description: The users last name
data:
build: faker.name.lastName()
username:
type: string
description: The username
data:
build: faker.internet.userName()
password:
type: string
description: The users password
data:
build: faker.internet.password()
email_address:
type: string
description: The users email address
data:
build: faker.internet.email()
created_on:
type: integer
description: An epoch time of when the user was created
data:
build: new Date(faker.date.past()).getTime()
Let's say that we have a new requirement where we have to support a home and work address for each user. Based on this requirement, we have decided to create a top-level property named addresses that will contain nested properties of home and work.
{
...
"addresses": {
"home": {
"address_1": "123 Broadway St",
"address_2": "Apt. C",
"locality": "Greensboro",
"region": "NC",
"postal_code": "27409",
"country": "US"
},
"work": {
"address_1": "321 Morningside Ave",
"address_2": "",
"locality": "Greensboro",
"region": "NC",
"postal_code": "27409",
"country": "US"
}
}
}
Our users.yaml
model will need to be updated to support these new address properties.
For brevity, the other properties have been left off of the model definition.
...
properties:
...
addresses:
type: object
description: An object containing the home and work addresses for the user
properties:
home:
type: object
description: The users home address
properties:
address_1:
type: string
description: The address 1
data:
build: `${faker.address.streetAddress()} ${faker.address.streetSuffix()}`
address_2:
type: string
description: The address 2
data:
build: chance.bool({ likelihood: 35 }) ? faker.address.secondaryAddress() : null
locality:
type: string
description: The city / locality
data:
build: faker.address.city()
region:
type: string
description: The region / state / province
data:
build: faker.address.stateAbbr()
postal_code:
type: string
description: The zip code / postal code
data:
build: faker.address.zipCode()
country:
type: string
description: The country code
data:
build: faker.address.countryCode()
work:
type: object
description: The users home address
properties:
address_1:
type: string
description: The address 1
data:
build: `${faker.address.streetAddress()} ${faker.address.streetSuffix()}`
address_2:
type: string
description: The address 2
data:
build: chance.bool({ likelihood: 35 }) ? faker.address.secondaryAddress() : null
locality:
type: string
description: The city / locality
data:
build: faker.address.city()
region:
type: string
description: The region / state / province
data:
build: faker.address.stateAbbr()
postal_code:
type: string
description: The zip code / postal code
data:
build: faker.address.zipCode()
country:
type: string
description: The country code
data:
build: faker.address.countryCode()
As you can see our home and work address properties contain the exact same nested properties. This duplication makes our model bigger and more verbose. Additionally, what happens if our address requirements need to change? We’d have to make two updates to keep the structure the same. This is where we can take advantage of definitions, by defining a single way to create an address and reference it.
For brevity, the other properties have been left off of the model definition.
...
properties:
...
addresses:
type: object
description: An object containing the home and work addresses for the user
properties:
home:
description: The users home address
schema:
$ref: '#/definitions/Address'
work:
description: The users work address
schema:
$ref: '#/definitions/Address'
definitions:
Address:
type: object
properties:
address_1:
type: string
description: The address 1
data:
build: `${faker.address.streetAddress()} ${faker.address.streetSuffix()}`
address_2:
type: string
description: The address 2
data:
build: chance.bool({ likelihood: 35 }) ? faker.address.secondaryAddress() : null
locality:
type: string
description: The city / locality
data:
build: faker.address.city()
region:
type: string
description: The region / state / province
data:
build: faker.address.stateAbbr()
postal_code:
type: string
description: The zip code / postal code
data:
build: faker.address.zipCode()
country:
type: string
description: The country code
data:
build: faker.address.countryCode()
Definitions are defined at the root of the model by specifying a definition: property, then the name of the definition. Definitions are referenced by using $ref
with the value being the path to the definition, for example, #/definitions/Address
. By using definitions, we have saved almost 30 lines of code in our model and created a single place for how an address is to be defined and generated within our Users model.
We can test the output of our updated Users model addresses using the following command:
fakeit console --count 1 models/users.yaml
Now, let's say we have a requirement to store a User's main phone number, and then store optional additional phone numbers i.e. Home, Work, Mobile, Fax, etc. For this change, we will use two new top-level properties: main_phone
and additional_phones
, which is an array.
{
...
"main_phone": {
"phone_number": "7852322322",
"extension": null
},
"additional_phones": [
{
"phone_number": "3368232032",
"extension": "3233",
"type": "Work"
},
{
"phone_number": "4075922921",
"extension": null,
"type": "Mobile"
}
]
}
While this may not be the most practical data model, or how I personally would have modeled this data, we can use this example again to illustrate how we can take advantage of using definitions within our FakeIt model.
For brevity, the other properties have been left off of the model definition.
...
properties:
...
main_phone:
description: The users main phone number
schema:
$ref: '#/definitions/Phone'
data:
post_build: |
delete this.main_phone.type
return this.main_phone
additional_phones:
type: array
description: The users additional phone numbers
items:
$ref: '#/definitions/Phone'
data:
min: 1
max: 4
definitions:
Phone:
type: object
properties:
type:
type: string
description: The phone type
data:
build: faker.random.arrayElement([ 'Home', 'Work', 'Mobile', 'Other' ])
phone_number:
type: string
description: The phone number
data:
build: faker.phone.phoneNumber().replace(/[^0-9]+/g, '')
extension:
type: string
description: The phone extension
data:
build: chance.bool({ likelihood: 30 }) ? chance.integer({ min: 1000, max: 9999 }) : null
For this example, we have created a Phone definition that contains three properties: type
, phone_number
, and extension
. You will notice that we have defined a post_build
function on the main_phone
property that removes the type attribute. This is illustrating how definitions can be used in conjunction with a build function to manipulate what is returned by the definition. The additional_phones
property is an array of Phone definitions that will generate between one to four phones. We can test the output of our updated Users model phone numbers using the following command:
fakeit console --count 1 models/users.yaml
Conclusion
Definitions allow you to streamline your FakeIt models by creating reusable sets of code for smaller more efficient models.
Comments