In our tests we often need to create a bunch of test-objects that are populated with random-data. This data needs to follow specific rules as identifiers need to be unique or must be incremented, string-properties must follow special conventions and so on. In the following short tutorial I will demonstrate how to generate such test data using the Fixture Factory library.

fixture factory running with junit in intellij 1024x752
Figure 1. Fixture Factory and JUnit

Prerequisites

We need to add one dependency for fixture-factory to our Mavenized project’s pom.xml:

<dependency>
  <groupId>br.com.six2six</groupId>
  <artifactId>fixture-factory</artifactId>
  <version>3.1.0</version>
  <scope>test</scope>
</dependency>

I’m using JUnit and AssertJ for writing sample tests in the following tutorial, please feel free to take a deeper look at my complete pom.xml in the section "Tutorial Sources".

Entities

These are the two entities that we wish to generate test-data for.

Book Entity

A book has an id, a title, a creation-date and one-to-many relation – it may have authors.

package com.hascode.tutorial.entity;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

public class Book {
  private Long id;
  private String title;
  private List<Author> authors = new ArrayList<>();
  private Date created;

 // getter, setter, toString ommitted...
}

Author Entity

An author simply has a first-name, a last-name and an e-mail address.

package com.hascode.tutorial.entity;

public class Author {
  private String firstName;
  private String lastName;
  private String email;

  // getter, setter, toString ommitted..
}

Existing Data Generators

There are different existing data-generators that we may use here to populate our bean properties .. e.g.:

  • Random: Picks a random element from a list of specified items (Documentation)

  • Regex: Creates data based on a regular expression (Documentation)

  • Date: Different functions to create dates (before/after/random/now..) (Documentation)

  • Names: Random names from lists of first- and last-names (Documentation)

  • Unique Random: Unique value from a list of elements (Documentation)

  • CNPJ: Generates an identification number (Documentation)

Creating  Templates

Templates are assigned on a class basis and are created with a label. In the first example, we’re creating a template for authors with a label named "valid". We’re using name-generators for the author’s first-name and last-name and the string-interpolation feature to populate the e-mail address.

Fixture.of(Author.class).addTemplate("valid", new Rule() {{
  add("firstName", firstName());
  add("lastName", lastName());
  add("email", "${firstName}.${lastName}@email.com");
}});

For generating books, we’re adding the following template, also named "valid" and apply the following generator rules: The id is filled with a random-number in a range between 1 and 200, the authors are filled with two authors using the author-template named "valid" above. Finally the title is filled with data matching a given regular-expression (regex) and the created-date is filled with some date after a given date-string with a specific date-format applied.

Fixture.of(Book.class).addTemplate("valid", new Rule() {{
  add("id", random(Long.class, range(1L, 200L)));
  add("authors", has(2).of(Author.class, "valid"));
  add("title", regex("[A-Z]{1}[A-Z a-z]{9,29}"));
  add("created", afterDate("2017-06-22", new SimpleDateFormat("yyyy-MM-dd")));
}});

JUnit Example

In the following example, we’re using our data-templates in a JUnit test. We’re first generating one book and then twenty books that are filled according to our template-rules.

package com.hascode.tutorial;

import static org.assertj.core.api.Assertions.assertThat;

import br.com.six2six.fixturefactory.Fixture;
import br.com.six2six.fixturefactory.Rule;
import com.hascode.tutorial.entity.Author;
import com.hascode.tutorial.entity.Book;
import java.text.SimpleDateFormat;
import java.util.List;
import org.junit.Test;

public class FixtureExampleTest {

  @Test
  public void testWithBooks() throws Exception {
    Fixture.of(Author.class).addTemplate("valid", new Rule() {{
      add("firstName", firstName());
      add("lastName", lastName());
      add("email", "${firstName}.${lastName}@email.com");
    }});

    Fixture.of(Book.class).addTemplate("valid", new Rule() {{
      add("id", random(Long.class, range(1L, 200L)));
      add("authors", has(2).of(Author.class, "valid"));
      add("title", regex("[A-Z]{1}[A-Z a-z]{9,29}"));
      add("created", afterDate("2017-06-22", new SimpleDateFormat("yyyy-MM-dd")));
    }});

    Book oneBook = Fixture.from(Book.class).gimme("valid");

    assertThat(oneBook.getId()).isBetween(1L, 1000L)
        .as("id must be between 1 and 1000, value is: %s", oneBook.getId());
    assertThat(oneBook.getTitle()).isNotBlank();
    assertThat(oneBook.getAuthors()).hasSize(2);
    // and so on ..

    System.out.println(oneBook);

    List<Book> twentyBooks = Fixture.from(Book.class).gimme(20, "valid");
    assertThat(twentyBooks).hasSize(20);

    // jff
    twentyBooks.forEach(System.out::println);
  }
}

If we run our tests using our IDE of choice or in the command-line, we should be able to see an output similar to this one:

$ mvn test -Dtest=FixtureExampleTest
-------------------------------------------------------
 T E S T S
-------------------------------------------------------
Running com.hascode.tutorial.FixtureExampleTest
Book{id=16, title='RKLNvVPdVqJQCbrAAUxjALl', authors=[Author{firstName='Ludivina', lastName='Lubowitz', email='Ludivina.Lubowitz@email.com'}, Author{firstName='Lorenza', lastName='Lebsack', email='Lorenza.Lebsack@email.com'}], created=Thu Aug 21 08:27:53 CEST 2031}
Book{id=95, title='XkVTQatRigjgeCnpWqBcmDt', authors=[Author{firstName='Sunshine', lastName='Homenick', email='Sunshine.Homenick@email.com'}, Author{firstName='Drucilla', lastName='Predovic', email='Drucilla.Predovic@email.com'}], created=Thu Nov 27 09:10:16 CET 2036}
Book{id=78, title='YVybMwYegJfdYPLfSqhcyZi', authors=[Author{firstName='Brigette', lastName='Connelly', email='Brigette.Connelly@email.com'}, Author{firstName='Marielle', lastName='Kassulke', email='Marielle.Kassulke@email.com'}], created=Mon Apr 29 16:06:03 CEST 2041}
Book{id=119, title='DIuJKotftB pQMrJnAUHpQc', authors=[Author{firstName='Jonathan', lastName='Eichmann', email='Jonathan.Eichmann@email.com'}, Author{firstName='Deangelo', lastName='O'Conner', email='Deangelo.O'Conner@email.com'}], created=Thu Mar 25 10:52:19 CET 2032}
Book{id=94, title='Ti MgkZdnwqJoMIeuLbzrWL', authors=[Author{firstName='Brittney', lastName='Lindgren', email='Brittney.Lindgren@email.com'}, Author{firstName='Mckinley', lastName='Medhurst', email='Mckinley.Medhurst@email.com'}], created=Tue Aug 06 03:10:00 CEST 2041}
Book{id=111, title='CRxQlInmuyZjZNf GiuSuAc', authors=[Author{firstName='Leonarda', lastName='Anderson', email='Leonarda.Anderson@email.com'}, Author{firstName='Virgilio', lastName='O'Conner', email='Virgilio.O'Conner@email.com'}], created=Thu Dec 09 16:49:34 CET 2038}
Book{id=9, title='XSVBSjQXjJSuyWMBDYgkLhN', authors=[Author{firstName='Alphonse', lastName='Thompson', email='Alphonse.Thompson@email.com'}, Author{firstName='Dominick', lastName='Bogisich', email='Dominick.Bogisich@email.com'}], created=Wed Feb 20 08:47:30 CET 2041}
Book{id=178, title='QUosEgiTvMoTVuhkOBJZzJy', authors=[Author{firstName='Benjamin', lastName='Ondricka', email='Benjamin.Ondricka@email.com'}, Author{firstName='Angelica', lastName='Medhurst', email='Angelica.Medhurst@email.com'}], created=Mon Jul 10 14:14:32 CEST 2023}
Book{id=91, title='GpignmSYdevlwvtDhxvpoWN', authors=[Author{firstName='Brigitte', lastName='Schmeler', email='Brigitte.Schmeler@email.com'}, Author{firstName='Santiago', lastName='Champlin', email='Santiago.Champlin@email.com'}], created=Thu Dec 30 13:40:18 CET 2021}
Book{id=181, title='K NspcTnTTObOoqEys gVAU', authors=[Author{firstName='Christia', lastName='Turcotte', email='Christia.Turcotte@email.com'}, Author{firstName='Eleanora', lastName='Gislason', email='Eleanora.Gislason@email.com'}], created=Mon Jan 12 13:13:51 CET 2043}
Book{id=80, title='NnElpENDcfPUQpBox xhFzv', authors=[Author{firstName='Wendolyn', lastName='Schmeler', email='Wendolyn.Schmeler@email.com'}, Author{firstName='Asuncion', lastName='Schuster', email='Asuncion.Schuster@email.com'}], created=Wed Feb 19 21:17:15 CET 2020}
Book{id=163, title='LsjbzrRTjYHlEnTtaFHvNvw', authors=[Author{firstName='Terrence', lastName='Lindgren', email='Terrence.Lindgren@email.com'}, Author{firstName='Fletcher', lastName='Tremblay', email='Fletcher.Tremblay@email.com'}], created=Tue Dec 12 18:22:17 CET 2028}
Book{id=169, title='FOcPqZIPemhPCoGoMqgZuAm', authors=[Author{firstName='Maryetta', lastName='Bernhard', email='Maryetta.Bernhard@email.com'}, Author{firstName='Elizabet', lastName='Donnelly', email='Elizabet.Donnelly@email.com'}], created=Fri Sep 04 02:00:55 CEST 2020}
Book{id=145, title='GrpfZQVssACDeCMloEHqJLB', authors=[Author{firstName='Cristina', lastName='Medhurst', email='Cristina.Medhurst@email.com'}, Author{firstName='Brunilda', lastName='Nitzsche', email='Brunilda.Nitzsche@email.com'}], created=Fri Jun 30 16:49:30 CEST 2023}
Book{id=112, title='RmgdtTeayJxZJYJWtYDacCu', authors=[Author{firstName='Francina', lastName='Reynolds', email='Francina.Reynolds@email.com'}, Author{firstName='Angelena', lastName='Johnston', email='Angelena.Johnston@email.com'}], created=Fri Oct 06 04:23:41 CEST 2017}
Book{id=4, title='SjDXqpoKBvkggEKVzQX hJT', authors=[Author{firstName='Lizabeth', lastName='Kshlerin', email='Lizabeth.Kshlerin@email.com'}, Author{firstName='Giuseppe', lastName='Bogisich', email='Giuseppe.Bogisich@email.com'}], created=Tue Sep 28 13:08:20 CEST 2032}
Book{id=151, title='ZLmDYpvGJOsyWJpCmOCXCad', authors=[Author{firstName='Juliette', lastName='Franecki', email='Juliette.Franecki@email.com'}, Author{firstName='Randolph', lastName='Shanahan', email='Randolph.Shanahan@email.com'}], created=Tue Aug 16 09:46:48 CEST 2033}
Book{id=25, title='IwnnNvTSoumUNODLpcNKYMa', authors=[Author{firstName='Wendolyn', lastName='Tremblay', email='Wendolyn.Tremblay@email.com'}, Author{firstName='Genevive', lastName='Luettgen', email='Genevive.Luettgen@email.com'}], created=Fri Jul 27 01:07:45 CEST 2018}
Book{id=63, title='UXKyM QtLLSlImZUpMpobkF', authors=[Author{firstName='Fredrick', lastName='Leuschke', email='Fredrick.Leuschke@email.com'}, Author{firstName='Pasquale', lastName='Bernhard', email='Pasquale.Bernhard@email.com'}], created=Tue Mar 15 02:19:59 CET 2033}
Book{id=43, title='TAchTVvIWZbadWHJJxBOFFf', authors=[Author{firstName='Mauricio', lastName='Anderson', email='Mauricio.Anderson@email.com'}, Author{firstName='Elfrieda', lastName='Shanahan', email='Elfrieda.Shanahan@email.com'}], created=Thu Jul 21 04:54:47 CEST 2044}
Book{id=106, title='FyLXvOckoCEo DuTwC CMpK', authors=[Author{firstName='Faustino', lastName='O'Conner', email='Faustino.O'Conner@email.com'}, Author{firstName='Claretha', lastName='O'Reilly', email='Claretha.O'Reilly@email.com'}], created=Wed Jan 18 09:47:47 CET 2034}
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.354 sec

Results :

Tests run: 1, Failures: 0, Errors: 0, Skipped: 0

Re-using Templates

We may create templates based on other existing templates. In the following example, we’re creating a new author template with name "invalid" based on the "valid" template and change the rule for last-name to populate this field with an empty string.

Fixture.of(Author.class).addTemplate("valid", new Rule() {{
  add("firstName", firstName());
  add("lastName", lastName());
  add("email", "${firstName}.${lastName}@email.com");
}});

Fixture.of(Author.class).addTemplate("invalid").inherits("valid", new Rule(){{
  add("lastName", "");
}});

JUnit Example

In  this example, we’re creating a new template based on an existing author template as described above.

package com.hascode.tutorial;

import static org.assertj.core.api.Assertions.assertThat;

import br.com.six2six.fixturefactory.Fixture;
import br.com.six2six.fixturefactory.Rule;
import com.hascode.tutorial.entity.Author;
import org.junit.Test;

public class ReUsingTemplatesExampleTest {

  @Test
  public void testWithBooks() throws Exception {
    Fixture.of(Author.class).addTemplate("valid", new Rule() {{
      add("firstName", firstName());
      add("lastName", lastName());
      add("email", "${firstName}.${lastName}@email.com");
    }});

    Fixture.of(Author.class).addTemplate("invalid").inherits("valid", new Rule() {{
      add("lastName", "");
    }});

    Author author = Fixture.from(Author.class).gimme("invalid");
    assertThat(author.getLastName()).isEmpty();

    System.out.println(author);
  }
}

Running the JUnit Test

We may run our example like this:

$ mvn test -Dtest=ReUsingTemplatesExampleTest
-------------------------------------------------------
 T E S T S
-------------------------------------------------------
Running com.hascode.tutorial.ReUsingTemplatesExampleTest
Author{firstName='Lovie', lastName='', email='Lovie.@email.com'}
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.247 sec

Results :

Tests run: 1, Failures: 0, Errors: 0, Skipped: 0

Using Template Loaders

We may transfer our templates to a dedicated class so that our tests may load our templates using Fixture Factory‘s template loaders. To create a template loader we must simply implement TemplateLoader and create our templates in its load() method.

package com.hascode.testing.mytemplates;

import br.com.six2six.fixturefactory.Fixture;
import br.com.six2six.fixturefactory.Rule;
import br.com.six2six.fixturefactory.loader.TemplateLoader;
import com.hascode.tutorial.entity.Author;
import com.hascode.tutorial.entity.Book;
import java.text.SimpleDateFormat;

public class MyTemplateLoader implements TemplateLoader {

  @Override
  public void load() {
    Fixture.of(Author.class).addTemplate("valid", new Rule() {{
      add("firstName", firstName());
      add("lastName", lastName());
      add("email", "${firstName}.${lastName}@email.com");
    }});

    Fixture.of(Book.class).addTemplate("valid", new Rule() {{
      add("id", random(Long.class, range(1L, 200L)));
      add("authors", has(2).of(Author.class, "valid"));
      add("title", regex("[A-Z]{1}[A-Z a-z]{9,29}"));
      add("created", afterDate("2017-06-22", new SimpleDateFormat("yyyy-MM-dd")));
    }});

    Fixture.of(Author.class).addTemplate("invalid").inherits("valid", new Rule() {{
      add("lastName", "");
    }});
  }
}

JUnit Example

Afterward we may load the templates using the FixtureFactoryLoader like this:

package com.hascode.tutorial;

import static org.assertj.core.api.Assertions.assertThat;

import br.com.six2six.fixturefactory.Fixture;
import br.com.six2six.fixturefactory.loader.FixtureFactoryLoader;
import com.hascode.tutorial.entity.Author;
import org.junit.BeforeClass;
import org.junit.Test;

public class TemplateLoaderExampleTest {

  @BeforeClass
  public static void setupTest() {
    FixtureFactoryLoader.loadTemplates("com.hascode.testing.mytemplates");
  }

  @Test
  public void testAuthor() throws Exception {
    Author valid = Fixture.from(Author.class).gimme("valid");
    Author invalid = Fixture.from(Author.class).gimme("invalid");

    assertThat(valid.getLastName()).isNotEmpty();
    assertThat(invalid.getLastName()).isEmpty();

    System.out.printf("valid: %s\n", valid);
    System.out.printf("invalid: %s\n", invalid);
  }
}

Running the JUnit Example

Again we may run our tests like this:

$ mvn test -Dtest=TemplateLoaderExampleTest
-------------------------------------------------------
 T E S T S
-------------------------------------------------------
Running com.hascode.tutorial.TemplateLoaderExampleTest
valid: Author{firstName='Anderson', lastName='Nitzsche', email='Anderson.Nitzsche@email.com'}
invalid: Author{firstName='Alphonso', lastName='', email='Alphonso.@email.com'}
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.239 sec

Results :

Tests run: 1, Failures: 0, Errors: 0, Skipped: 0

Using Processors

Processors allow us to interact with our generated test-data e.g. for saving them to a development database (pre-defined Hibernate processors exist). We simply need to implement Processor and we receive the generated test-object as parameter.

package com.hascode.testing.myprocessors;

import br.com.six2six.fixturefactory.processor.Processor;

public class CustomPersistenceProcessor implements Processor {

  @Override
  public void execute(Object o) {
    System.out.printf("persisting test-data: %s\n", o);
  }
}

JUnit Example

We may apply the processor now as shown in the following test case.

package com.hascode.tutorial;

import static org.assertj.core.api.Assertions.assertThat;

import br.com.six2six.fixturefactory.Fixture;
import br.com.six2six.fixturefactory.loader.FixtureFactoryLoader;
import com.hascode.testing.myprocessors.CustomPersistenceProcessor;
import com.hascode.tutorial.entity.Author;
import java.util.List;
import org.junit.BeforeClass;
import org.junit.Test;

public class ProcessorExampleTest {

  @BeforeClass
  public static void setupTest() {
    FixtureFactoryLoader.loadTemplates("com.hascode.testing.mytemplates");
  }

  @Test
  public void testAuthor() throws Exception {
    List<Author> authors = Fixture.from(Author.class).uses(new CustomPersistenceProcessor())
        .gimme(10, "valid");
    assertThat(authors).hasSize(10);
  }
}

Running the JUnit Example

Running our test should produce a similar output:

$ mvn test -Dtest=ProcessorExampleTest
-------------------------------------------------------
 T E S T S
-------------------------------------------------------
Running com.hascode.tutorial.ProcessorExampleTest
persisting test-data: Author{firstName='Nada', lastName='Mann', email='Nada.Mann@email.com'}
persisting test-data: Author{firstName='Troy', lastName='Koch', email='Troy.Koch@email.com'}
persisting test-data: Author{firstName='Viki', lastName='Mohr', email='Viki.Mohr@email.com'}
persisting test-data: Author{firstName='Doug', lastName='Mohr', email='Doug.Mohr@email.com'}
persisting test-data: Author{firstName='Abby', lastName='Auer', email='Abby.Auer@email.com'}
persisting test-data: Author{firstName='Gail', lastName='Moen', email='Gail.Moen@email.com'}
persisting test-data: Author{firstName='Drew', lastName='Hane', email='Drew.Hane@email.com'}
persisting test-data: Author{firstName='Kory', lastName='Will', email='Kory.Will@email.com'}
persisting test-data: Author{firstName='Dann', lastName='Jast', email='Dann.Jast@email.com'}
persisting test-data: Author{firstName='Alex', lastName='Funk', email='Alex.Funk@email.com'}
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.284 sec

Results :

Tests run: 1, Failures: 0, Errors: 0, Skipped: 0

Tutorial Sources

Please feel free to download the tutorial sources from my GitHub repository, fork it there or clone it using Git:

git clone https://github.com/hascode/fixture-factory-tutorial.git

Article Updates

  • 2017-07-03: Typo in the name generator examples fixed.