Template Driven Test POJO Generation with Fixture Factory and Java

June 20th, 2017 by

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 and JUnit

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 Bitbucket repository, fork it there or clone it using Git:

git clone https://bitbucket.org/hascode/fixture-factory-tutorial.git

Resources

Other Testing Tutorials of mine

Please feel free to have a look at other testing tutorial of mine (an excerpt):

And more…

Article Updates

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

Tags: , , , , , , , , , ,

Search
Categories