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.
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
Resources
Other Testing Tutorials of mine
Please feel free to have a look at other testing tutorial of mine (an excerpt):
Article Updates
-
2017-07-03: Typo in the name generator examples fixed.