Beanmother helps to create your various and complex objects super easily with fixtures for testing. It encourages developers to write more tests.
Beanmother is a implementation of ObjectMother pattern and also fixture replacement tool. You do not need to write extra code like factories or builders for creating test objects. Beanmother helps to create fresh and randomized bean objects for every single test. You can use your beans as it is.
Fixtures can be written in YAML format that is very easy to read and write. It is expressive and extensible. You can use the scripts provided by Beanmother to create multiple types of random data and global sequentail numbers.
Java 7 and above are supported.
- Apache Maven
<dependency>
<groupId>io.beanmother</groupId>
<artifactId>beanmother-core</artifactId>
<version>0.7.2</version>
<scope>test</scope>
</dependency>
<!-- For java 8 time and optional datatype -->
<dependency>
<groupId>io.beanmother</groupId>
<artifactId>beanmother-java8-converter</artifactId>
<version>0.7.2</version>
<scope>test</scope>
</dependency>
- Gradle
testCompile 'io.beanmother:beanmother-core:0.7.2'
// For java 8 time and optional datatype
testCompile 'io.beanmother:beanmother-java8-converter:0.7.2'
Create fixture .yml
file in test/resources/fixtures
as a convention.
# test/resources/fixtures/publishing.yml
book: &book
title: ${faker.book.title}
language: en
publishedAt: ${faker.date.between('2000-01-01', '2010-01-01')}
author:
id: ${sequence.number}
name: ${faker.book.author}
introduction: ${faker.lorem.paragraph}
birth: ${faker.date.between('1990-01-01', '2000-01-01')}
gender: MALE
works:
- <<: *book
- <<: *book
- <<: *book
And, Just create!
ObjectMother objectMother = ObjectMother.getInstance();
@Test
public void testSingleObject() {
Book book = objectMother.bear("book", Book.class);
Author author = objectMother.bear("author", Author.class);
}
@Test
public void testMultipleObjects() {
List<Author> authors = objectMother.bear("author", Author.class, 10);
}
The scripts provided by Beanmother is a kind of fake property value generator.
author:
title: ${faker.book.title}
Currently, FakerScriptRunner
and SeqenceScriptRunner
are registered as a default.
FakerScriptRunner
works withfaker
namespace. the script runner is implemented by java-faker. you can find a usage of java-faker in document. If a method has no arguments, you can ignore parentheses. for example,
beer:
hop: ${faker.beer.hop}
malt: ${faker.beer.malt}
created_at: ${faker.date.between('1990-01-01', '2000-01-01')}
SequenceScriptRunner
works withsequence
namespace. the script generate sequential number globally. for example,
person:
id: ${sequence.number}
If a bean does not have no-argument contructor, just add _construct
key. for example,
price:
_construct:
- 3
- USD
If you need to common configuration for specific beans, you can use PostProcessor.
ObjectMother
class is a default implementation of AbstractBeanMother
class. For customization, simply extend AbstractBeanMother
. Highly recommended to build as a singleton instance.
public class MyObjectMother extends AbstractBeanMother {
private static MyObjectMother myObjectMother = new MyObjectMother();
private MyObjectMother() {
super();
}
// Override for adding your default fixture directory paths
@Override
public String[] defaultFixturePaths() {
return new String[]{ 'test-models', 'fixtures' };
}
// Override for adding your custom Converter.
@Override
protected void configureConverterFactory(ConverterFactory converterFactory) {
converterFactory.register(new MyConverter());
}
// Override for adding your custom ScriptRunner.
@Override
protected void configureScriptHandler(ScriptHandler scriptHandler) {
scriptHandler.register(new MyScriptRunner);
}
// Override for adding your custom PostProcessor.
@Override
protected void configurePostProcessorFactory(PostProcessorFactory postProcessorFactory) {
postProcessorFactory.register(new MyPostProcessor);
}
}
A PostProcessor can handle you bean after mapper.
public class AuthorPostProcessor extends PostProcessor<Author> {
@Override
public void process(Author bean, FixtureMap fixtureMap) {
for(Book book : bean.getWorks()) {
book.setAuthor(bean);
}
}
}
And pass the instance as a argument when you create a instance.
Author author = ObjectMother.bear("author", Author.class, new AuthorPostProcessor());
or, register the PostProcessor to your custom BeanMother for using globally.
@Override
protected void configurePostProcessorFactory(PostProcessorFactory postProcessorFactory) {
postProcessorFactory.register(new AuthorPostProcessor());
}
Everytime you create a instance of Author, AuthorPostProcessor
will run before return a instance of Author.
Just register the path in ObjectMother. It wil scan all files under the path.
ObjectMother.addFixtureLocation("mocks");
Or, override #defaultFixturePaths
in your custom BeanMother.
@Override
public String[] defaultFixturePaths() {
// Add your fixture directory or file paths under `resources`.
return new String[]{ 'test-models', 'fixtures' };
}
You can write your own converter for some reason.
public class MyIntegerToStringConverter extends AbstractGenericConverter<Integer, String> {
@Override
public String convert(Integer source) {
return String.valueOf(source + 1);
}
}
And, register the Converter in your custom BeanMother.
@Override
protected void configureConverterFactory(ConverterFactory converterFactory) {
converterFactory.register(new MyIntegerToStringConverter());
}
You can write your own ScriptRunner
public class MyScriptRunner implements ScriptRunner {
@Override
public Object run(ScriptFragment scriptFragment) {
// Do something
return any;
}
@Override
public boolean canHandle(ScriptFragment scriptFragment) {
return scriptFragment.getMethodName.equal("myname");
}
}
And, register the ScriptRunner in your custom BeanMother.
@Override
protected void configureScriptHandler(ScriptHandler scriptHandler) {
scriptHandler.register(new MyScriptRunner());
}
Any kind of contributions are very welcome! Coding style guideline is not prepared yet. Although I use Intellij IDE default style, follow a common sense you believe except 4 space indentation.