• Stars
    star
    9
  • Rank 1,873,298 (Top 39 %)
  • Language
    Scala
  • License
    Apache License 2.0
  • Created about 9 years ago
  • Updated about 7 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Randomised, zero-boilerplate object builders

databob

(Also available in JavaScript and Kotlin flavours)

Databob provides a way to generate completely randomised object builders with zero-boilerplate code.

Why?

The problem of generating dummy test instances for our classes has been around for a long time. Given the following case classes...

case class ReadReceipt(value: Boolean)

case class EmailAddress(value: String)

case class Email(from: EmailAddress, to: Seq[EmailAddress], date: ZonedDateTime, read: Boolean, subject: String, readReceipt: Try[ReadReceipt])

case class Inbox(address: EmailAddress, emails: Seq[Email])

We could start to write objects using the TestBuilder pattern using the traditional method:

class InboxAddressBuilder {
  private var address = EmailAddress("[email protected]")
  private var emails = Seq[Email]()

  def withAddress(newAddress: EmailAddress) = {
    address = newAddress
    this
  }

  def withEmails(newEmails: Seq[Email]) = {
    emails = newEmails
    this
  }

  def build = Inbox(address, emails)
}

Scala makes this easier for us somewhat by leveraging Case class copy(). This also allows us to be compiler safe, as removing a field will break the equivalent with method:

class InboxAddressBuilder {
  private var inbox = Inbox(EmailAddress("[email protected]"), Seq[Email]())
  
  def withAddress(newAddress: EmailAddress) = {
    inbox = inbox.copy(address = newAddress)
    this
  }

  def withEmails(newEmails: Seq[Email]) = {
    inbox = inbox.copy(emails = newEmails)
    this
  }

  def build = inbox
}

Taking this even further with default arguments, we can reduce this to:

object InboxAddressBuilder {
  def apply(address: EmailAddress = EmailAddress("[email protected]"),
            emails: Seq[Email] = Nil)
    = Inbox(address, emails)
}

So, better - but it still seems pretty tedious to maintain. Additionally, we don't really want tests to rely unknowingly on bits of default test data for multiple tests which will lead to an explosion of ObjectMother-type methods with small variations to suit particular tests.

What we really want are completely randomised instances, with important overrides set-up only for tests that rely on them. No sharing of test data across tests. Ever.

No sharing of test data across tests. Ever.

Enter Databob

For a completely randomised instance, including non-primitive sub-tree objects:

Databob.random[Email]

That's it. Want to override particular value(s)?

Databob.random[Inbox].copy(address = EmailAddress("[email protected]"))

Or add your own rule for generating values?

implicit val generators = typeIs(databob => EmailAddress(databob.mk[String] + "@" + databob.mk[String] + ".com")) +: Generators.EmptyGenerators

Databob.random[Email]

Additionally, once we have this randomising structure in code, we can use it for other useful stuff. For example, generating JSON with a library such as Json4S:

case class Book(title: String, pages: Int)

case class Teacher(firstName: String, lastName: String)

case class SchoolLibrary(librarian: Teacher, books: Seq[Book])

implicit val f = DefaultFormats
pretty(render(decompose(Databob.random[SchoolLibrary])))

... would generate us something like this:

{
  "librarian":{
    "firstName":"6c550709-bdc8-4ce8-8acd-607020f027bb",
    "lastName":"11073325-20fb-4d81-832c-d2eacd5bc4f1"
  },
  "books":[{
    "title":"982c7e30-a969-40f1-99c1-f397d1c52494",
    "pages":713182742
  }]
}

Or to get XML...

<SchoolLibrary>
  {Xml.toXml(decompose(Databob.random[SchoolLibrary]))}
</SchoolLibrary>

...producing this:

<SchoolLibrary>
    <librarian>
        <firstName>e1981fac-f3f4-4abf-82e4-374975d2b585</firstName>
        <lastName>75a13eca-1ee0-4ec0-aff4-1ab3026b5acf</lastName>
    </librarian>
    <books>
        <title>50029977-7566-43d5-83fa-09affdcbd7d5</title>
        <pages>1502236860</pages>
    </books>
    <books>
        <title>d25a697c-8960-4d9b-b595-224ac07df78a</title>
        <pages>1777810872</pages>
    </books>
</SchoolLibrary>

Out-of-the-box features:

  • Nested object-trees (ie. non-primitive fields)
  • All Scala/Java primitives: Default, random
  • Scala and Java Collection classes: Empty, single-value, variable size, random
  • Java8 date-time values: Epoch, current-time, random
  • Some monadic types (Option/Either/Try/Future): Happy, unhappy, random
  • Simple overriding mechanism for your own-types and custom generation rules

See it in action

See the example code.

Get it

Add the following lines to build.sbt:

resolvers += "JCenter" at "https://jcenter.bintray.com"
libraryDependencies += "io.github.daviddenton" %% "databob" % "1.5.0"

Contribute

PRs gratefully accepted for other common types that might be useful.

Acks

To Json4S for the ninja reflection util.

More Repositories

1

fintrospect

Implement fast, type-safe HTTP webservices for Finagle
Scala
91
star
2

refactoring-golf

A Refactoring Golf exercise
Java
51
star
3

protokruft

Generate a DSL to hide all the hideous GRPC boilerplate
Kotlin
20
star
4

http4kbox

Multi-platform http4k demo application
Kotlin
19
star
5

databob.kotlin

Randomised, zero-boilerplate object builders
Kotlin
15
star
6

zeit

Clock and task scheduler for node.js applications, providing extensive control of time and callback scheduling in prod and test code
JavaScript
12
star
7

configur8

Nano-library which provides the ability to define typesafe (!) configuration templates for applications.
Scala
11
star
8

hamsandwich

This micro-library provides Java extensions to the Hamcrest library to provide a convienient way of declaring and combining entity Matchers which can be used in either test or production code.
Java
7
star
9

databob

Random object generation for tests
JavaScript
6
star
10

balderdash

Builders for monitoring dashboards
Python
5
star
11

finagle-circuit

Http circuit-breaking for Finagle
Scala
5
star
12

springclean

Now is the Spring of our discontent. Made glorious Summer.
Java
4
star
13

http4k-demo-s3box

S3-backed Dropbox implemented in 100 lines http4k
Kotlin
4
star
14

dotfiles

Shell
3
star
15

daviddenton

README
2
star
16

fintrospect-todo-backend

A simple fintrospect implementation for http://www.todobackend.com
Scala
2
star
17

fintrospect-example-app

An example application project for the Fintrospect library
JavaScript
2
star
18

featherweight

Kotlin
1
star
19

q-ext

Q extensions
JavaScript
1
star
20

lensparty

Kotlin
1
star
21

crossyfield

Pico-library for providing cross-field validation to Scala projects
Scala
1
star
22

finagle-dojo

Scala
1
star
23

niceassert

Improving the readability of test code
Java
1
star
24

http4k-k8s-api

Kotlin
1
star
25

metrique

Hierarchical metrics sent to StatsD.
Java
1
star
26

alpha-global

1
star
27

http4k-data-class-gen

Kotlin
1
star
28

http4k-blockchain

Kotlin
1
star