Skip to main content

Testing with random values

This covers a lot of interesting ground, test-wise.

If you enable enableTestInserts in typo.Options you now get an TestInsert class, with a method to insert a row for each table Typo knows about. All values except ids, foreign keys and so on are randomly generated, but you can override them with named parameters.

The idea is that you:

  • can easily insert rows for testing
  • can explicitly set the values you do care about
  • will get random values for the rest
  • are still forced to follow FKs to setup the data graph correctly
  • it's easy to follow those FKs, because after inserting a row you get the persisted version back, including generated IDs
  • can get the same values each time by hard coding the seed new TestInsert(new scala.util.Random(0L)), or you can run it multiple times with different seeds to see that the random values really do not matter
  • do not need to write any code to get all this available to you, like the rest of Typo.

In summary, this is a fantastic way of setting up complex test scenarios in the database!

Domains

If you use postgres domains you typically want affect the generation of data yourself. For that reason there is a trait you need to implement and pass in. This only affect you if you use domains.

import adventureworks.public.*

import scala.util.Random

// apply domain-specific rules here
object DomainInsert extends adventureworks.TestDomainInsert {
override def publicAccountNumber(random: Random): AccountNumber = AccountNumber(random.nextString(10))
override def publicFlag(random: Random): Flag = Flag(random.nextBoolean())
override def publicMydomain(random: Random): Mydomain = Mydomain(random.nextString(10))
override def publicName(random: Random): Name = Name(random.nextString(10))
override def publicNameStyle(random: Random): NameStyle = NameStyle(random.nextBoolean())
override def publicPhone(random: Random): Phone = Phone(random.nextString(10))
override def publicShortText(random: Random): ShortText = ShortText(random.nextString(10))
override def publicOrderNumber(random: Random): OrderNumber = OrderNumber(random.nextString(10))
}

Usage example

import adventureworks.customtypes.{Defaulted, TypoShort, TypoLocalDateTime, TypoXml}
import adventureworks.production.unitmeasure.UnitmeasureId
import adventureworks.TestInsert

import scala.util.Random

val testInsert = new TestInsert(new Random(0), DomainInsert)
// testInsert: TestInsert = adventureworks.TestInsert@429075c8

val unitmeasure = testInsert.productionUnitmeasure(UnitmeasureId("kgg"))
// unitmeasure: UnitmeasureRow = UnitmeasureRow(
// unitmeasurecode = UnitmeasureId(value = "kgg"),
// name = Name(value = "椕皿鈻瑜㶯眀㏝⑂Ᶎ䩇"),
// modifieddate = TypoLocalDateTime(value = 2024-06-21T15:59:30.773064)
// )
val productCategory = testInsert.productionProductcategory()
// productCategory: ProductcategoryRow = ProductcategoryRow(
// productcategoryid = ProductcategoryId(value = 194),
// name = Name(value = "℣ٿ玁冧ἀ蓆鋥射ⅅ匫"),
// rowguid = TypoUUID(value = 7b47c85a-2fd6-11ef-8bf6-0242ac160002),
// modifieddate = TypoLocalDateTime(value = 2024-06-21T15:59:30.773064)
// )
val productSubcategory = testInsert.productionProductsubcategory(productCategory.productcategoryid)
// productSubcategory: ProductsubcategoryRow = ProductsubcategoryRow(
// productsubcategoryid = ProductsubcategoryId(value = 194),
// productcategoryid = ProductcategoryId(value = 194),
// name = Name(value = "䪾悲켺я瘊ꖗ欖뢐豶㤖"),
// rowguid = TypoUUID(value = 7b4ceef2-2fd6-11ef-8bf6-0242ac160002),
// modifieddate = TypoLocalDateTime(value = 2024-06-21T15:59:30.773064)
// )
val productModel = testInsert.productionProductmodel(catalogdescription = Some(new TypoXml("<xml/>")), instructions = Some(new TypoXml("<instructions/>")))
// productModel: ProductmodelRow = ProductmodelRow(
// productmodelid = ProductmodelId(value = 194),
// name = Name(value = "衳⦥ፀ⟑骩誖ڠ鮩焸뭷"),
// catalogdescription = Some(value = TypoXml(value = "<xml/>")),
// instructions = Some(value = TypoXml(value = "<instructions/>")),
// rowguid = TypoUUID(value = 7b53416c-2fd6-11ef-8bf6-0242ac160002),
// modifieddate = TypoLocalDateTime(value = 2024-06-21T15:59:30.773064)
// )
testInsert.productionProduct(
safetystocklevel = TypoShort(1),
reorderpoint = TypoShort(1),
standardcost = BigDecimal(1),
listprice = BigDecimal(1),
daystomanufacture = 10,
sellstartdate = TypoLocalDateTime.now,
sizeunitmeasurecode = Some(unitmeasure.unitmeasurecode),
weightunitmeasurecode = Some(unitmeasure.unitmeasurecode),
`class` = Some("H "),
style = Some("W "),
productsubcategoryid = Some(productSubcategory.productsubcategoryid),
productmodelid = Some(productModel.productmodelid)
)
// res1: ProductRow = ProductRow(
// productid = ProductId(value = 194),
// name = Name(value = "睍먀缶쏄迄넼䒄䣃㓎俠"),
// productnumber = "JXQqPyuxbr589wyJzS2S",
// makeflag = Flag(value = true),
// finishedgoodsflag = Flag(value = true),
// color = Some(value = "iHrAOB2RuvBbFbQ"),
// safetystocklevel = TypoShort(value = 1),
// reorderpoint = TypoShort(value = 1),
// standardcost = 1,
// listprice = 1,
// size = Some(value = "NB7Zu"),
// sizeunitmeasurecode = Some(value = UnitmeasureId(value = "kgg")),
// weightunitmeasurecode = Some(value = UnitmeasureId(value = "kgg")),
// weight = None,
// daystomanufacture = 10,
// productline = None,
// class = Some(value = "H "),
// style = Some(value = "W "),
// productsubcategoryid = Some(value = ProductsubcategoryId(value = 194)),
// productmodelid = Some(value = ProductmodelId(value = 194)),
// sellstartdate = TypoLocalDateTime(value = 2024-06-21T15:59:31.054475),
// sellenddate = None,
// discontinueddate = Some(
// value = TypoLocalDateTime(value = 2034-05-19T00:51:56)
// ),
// rowguid = TypoUUID(value = 7b5a5830-2fd6-11ef-8bf6-0242ac160002),
// modifieddate = TypoLocalDateTime(value = 2024-06-21T15:59:30.773064)
// )

Comparison with scalacheck

This does look a lot like scalacheck indeed.

But look closer, there are:

  • no implicits
  • no integration glue code with test libraries
  • almost no imports, you need to mention very few types
  • no keeping track of all the possible row types and repositories
  • and so on

This feature is meant to be easy to use, and I really think/hope it is!