# gs-datagen [GS Open Source](https://garrity.co/oss.html) | [License (MIT)](./LICENSE) Test data generation library for Scala 3. `gs-datagen` provides _composable_ generator definitions that can be reused across tests. - [Usage](#usage) - [Imports](#imports) - [Examples](#examples) - [Example: Generate a Random User](#example-generate-a-random-user) - [Example: Require Input](#example-require-input) - [Supported Generators](#supported-generators) ## Usage ``` object Gs { val Datagen: ModuleID = "gs" %% "gs-datagen-core-v0" % "$VERSION" % Test } ``` ### Imports The standard way to use `gs-datagen` is to import the entire package, which pulls in `Gen[A]`, `Generated[A]` and `Datagen[A, -I]`: ```scala import gs.datagen.v0.* ``` ## Examples For the following examples, the following types (abbreviated) are relevant: ```scala opaque type Name = String opaque type DateOfBirth = LocalDate opaque type Karma = Long enum Role: case Regular, Mod, Admin case class User( name: Name, dateOfBirth: DateOfBirth, karma: Karma, role: Role ) ``` ### Example: Generate a Random User One way to go about this is to define generators for each type and then compose those generators for `User`: ```scala import gs.datagen.v0._ import gs.datagen.v0.generators.MinMax val nameGen: Gen[Name] = Gen.string.alpha(4, 16).map(Name(_)) val dateOfBirthGen: Gen[LocalDate] = Gen.date.beforeToday( days = MinMax.Zero, months = MinMax.nonNegative(0, 11), years = MinMax.nonNegative(18, 80) ).map(DateOfBirth(_)) val karmaGen: Gen[Karma] = Gen.long.inRange(-100L, 100L).map(Karma(_)) val roleGen: Gen[Role] = Gen.oneOf.fixedChoices(Role.Regular, Role.Mod, Role.Admin) ``` and the composition: ```scala val userGen: Gen[User] = for name <- nameGen dateOfBirth <- dateOfBirthGen karma <- karmaGen role <- roleGen yield User(name, dateOfBirth, karma, role) ``` Generators may also be defined inline if separate definitions are not useful: ```scala val userGen: Gen[User] = for name <- Gen.string.alpha(4, 16).map(Name(_)) dateOfBirth <- dateOfBirthGen karma <- Gen.long.inRange(-100L, 100L).map(Karma(_)) role <- roleGen yield User(name, dateOfBirth, karma, role) ``` Once that generator exists, users may be generated: ```scala val user: User = userGen.gen() ``` The `Generated` type class can be used as well: ```scala given Generated[User] = Generated.of(userGen) val user2: User = Generated[User].generate() ``` ### Example: Require Input What if we want a way to generate users randomly, but always require the caller to specify a user role? This can be accomplished by requiring input when generating data: ```scala val roleUserGen: Datagen[User, Role] = for name <- Gen.string.alpha(4, 16).map(Name(_)) dateOfBirth <- dateOfBirthGen karma <- Gen.long.inRange(-100L, 100L).map(Karma(_)) yield (role: Role) => User(name, dateOfBirth, karma, role) ``` The usage of this type is different and does not support `Generated`: ```scala val admin: User = roleUserGen.generate(Role.Admin) val mod: User = roleUserGen.generate(Role.Mod) val regular: User = roleUserGen.generate(Role.Regular) ``` ## Supported Generators For a complete list of generators supported out of the box, please refer to the [Gen](modules/core/src/main/scala/gs/datagen/v0/gen.scala) definition, which enumerates and documents all options. ## Donate Enjoy this project or want to help me achieve my [goals](https://garrity.co)? Consider [Donating to Pat on Ko-fi](https://ko-fi.com/gspfm).