Writing Clean Tests - New Considered Harmful

Creating new objects is an essential part of automated testing, and the most obvious way to do it is to use the new keyword.

However, this is not the best way to create new objects in our test cases, and using the new keyword will make our tests harder to read and maintain.

This blog post identifies the problems caused by the new keyword, and describes how we can solve these problems by using factory methods and the builder pattern.

New Is Not the New Black

During this tutorial we have been refactoring a unit test which ensures that the registerNewUserAccount(RegistrationForm userAccountData) method of the RepositoryUserService class works as expected when a new user account is created by using a unique email address and a social sign in provider.

The RegistrationForm class is a data transfer object (DTO), and our unit tests sets its property values by using setter methods. The source code of our unit test looks as follows (the relevant code is highlighted):

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.runners.MockitoJUnitRunner;
import org.mockito.stubbing.Answer;
import org.springframework.security.crypto.password.PasswordEncoder;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.mockito.Matchers.isA;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;


@RunWith(MockitoJUnitRunner.class)
public class RepositoryUserServiceTest {

    private static final String REGISTRATION_EMAIL_ADDRESS = "john.smith@gmail.com";
    private static final String REGISTRATION_FIRST_NAME = "John";
    private static final String REGISTRATION_LAST_NAME = "Smith";
    private static final Role ROLE_REGISTERED_USER = Role.ROLE_USER;
    private static final SocialMediaService SOCIAL_SIGN_IN_PROVIDER = SocialMediaService.TWITTER;

    private RepositoryUserService registrationService;

    @Mock
    private PasswordEncoder passwordEncoder;

    @Mock
    private UserRepository repository;

    @Before
    public void setUp() {
        registrationService = new RepositoryUserService(passwordEncoder, repository);
    }


    @Test
    public void registerNewUserAccount_SocialSignInAndUniqueEmail_ShouldCreateNewUserAccountAndSetSignInProvider() throws DuplicateEmailException       {
        RegistrationForm registration = new RegistrationForm();
        registration.setEmail(REGISTRATION_EMAIL_ADDRESS);
        registration.setFirstName(REGISTRATION_FIRST_NAME);
        registration.setLastName(REGISTRATION_LAST_NAME);
        registration.setSignInProvider(SOCIAL_SIGN_IN_PROVIDER);

        when(repository.findByEmail(REGISTRATION_EMAIL_ADDRESS)).thenReturn(null);

        when(repository.save(isA(User.class))).thenAnswer(new Answer<User>() {
            @Override
            public User answer(InvocationOnMock invocation) throws Throwable {
                Object[] arguments = invocation.getArguments();
                return (User) arguments[0];
            }
        });

        User createdUserAccount = registrationService.registerNewUserAccount(registration);

        assertEquals(REGISTRATION_EMAIL_ADDRESS, createdUserAccount.getEmail());
        assertEquals(REGISTRATION_FIRST_NAME, createdUserAccount.getFirstName());
        assertEquals(REGISTRATION_LAST_NAME, createdUserAccount.getLastName());
        assertEquals(SOCIAL_SIGN_IN_PROVIDER, createdUserAccount.getSignInProvider());
        assertEquals(ROLE_REGISTERED_USER, createdUserAccount.getRole());
        assertNull(createdUserAccount.getPassword());

        verify(repository, times(1)).findByEmail(REGISTRATION_EMAIL_ADDRESS);
        verify(repository, times(1)).save(createdUserAccount);
        verifyNoMoreInteractions(repository);
        verifyZeroInteractions(passwordEncoder);
    }
}

So, what is the problem? The highlighted part of our unit test is short and it is relatively easy to read. In my opinion, the biggest problem of this code is that it is data centric. It creates a new RegistrationForm object and sets the property values of the created object, but it doesn’t describe the meaning of these property values.

If we create new objects in the test method by using the new keyword, our tests become harder to read because:

  1. The reader has to know the different states of the created object. For example, if we think about our example, the reader has to know that if we create a new RegistrationForm object and set the property values of the email, firstName, lastName, and signInProvider properties, it means that the object is a registration which is made by using a social sign in provider.
  2. If the created object has many properties, the code which creates it, litters the source code of our tests. We should remember that even though we need these objects in our tests, we should focus on describing the behavior of the tested method / feature.

Although it isn’t realistic to assume that we can completely eliminate these drawbacks, we should do our best to minimize their effect and make our tests as easy to read as possible.

Let’s find out how we can do this by using factory methods.

Using Factory Methods

When we create new objects by using factory methods, we should name the factory methods and their method parameters in a such way that it makes our code easier to read and write. Let's take a look at two different factory methods and see what kind of an effect they have to the readability of our unit test.

These factory methods are typically added to an object mother class because often they are useful to more than one test class. However, because I want to keep things simple, I will add them directly to the test class.

The name of the first factory method is newRegistrationViaSocialSignIn(), and it has no method parameters. After we have added this factory method to our test class, the source of our unit test looks as follows (the relevant parts are highlighted):

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.runners.MockitoJUnitRunner;
import org.mockito.stubbing.Answer;
import org.springframework.security.crypto.password.PasswordEncoder;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.mockito.Matchers.isA;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;


@RunWith(MockitoJUnitRunner.class)
public class RepositoryUserServiceTest {

	private static final String REGISTRATION_EMAIL_ADDRESS = "john.smith@gmail.com";
	private static final String REGISTRATION_FIRST_NAME = "John";
	private static final String REGISTRATION_LAST_NAME = "Smith";
	private static final Role ROLE_REGISTERED_USER = Role.ROLE_USER;
	private static final SocialMediaService SOCIAL_SIGN_IN_PROVIDER = SocialMediaService.TWITTER;

	private RepositoryUserService registrationService;

	@Mock
	private PasswordEncoder passwordEncoder;

	@Mock
	private UserRepository repository;

	@Before
	public void setUp() {
		registrationService = new RepositoryUserService(passwordEncoder, repository);
	}


	@Test
	public void registerNewUserAccount_SocialSignInAndUniqueEmail_ShouldCreateNewUserAccountAndSetSignInProvider() throws DuplicateEmailException {
		RegistrationForm registration = newRegistrationViaSocialSignIn();

		when(repository.findByEmail(REGISTRATION_EMAIL_ADDRESS)).thenReturn(null);

		when(repository.save(isA(User.class))).thenAnswer(new Answer<User>() {
			@Override
			public User answer(InvocationOnMock invocation) throws Throwable {
				Object[] arguments = invocation.getArguments();
				return (User) arguments[0];
			}
		});

		User createdUserAccount = registrationService.registerNewUserAccount(registration);

		assertEquals(REGISTRATION_EMAIL_ADDRESS, createdUserAccount.getEmail());
		assertEquals(REGISTRATION_FIRST_NAME, createdUserAccount.getFirstName());
		assertEquals(REGISTRATION_LAST_NAME, createdUserAccount.getLastName());
		assertEquals(SOCIAL_SIGN_IN_PROVIDER, createdUserAccount.getSignInProvider());
		assertEquals(ROLE_REGISTERED_USER, createdUserAccount.getRole());
		assertNull(createdUserAccount.getPassword());

		verify(repository, times(1)).findByEmail(REGISTRATION_EMAIL_ADDRESS);
		verify(repository, times(1)).save(createdUserAccount);
		verifyNoMoreInteractions(repository);
		verifyZeroInteractions(passwordEncoder);
	}
	
	private RegistrationForm newRegistrationViaSocialSignIn() {
		RegistrationForm registration = new RegistrationForm();
	
		registration.setEmail(REGISTRATION_EMAIL_ADDRESS);
		registration.setFirstName(REGISTRATION_FIRST_NAME);
		registration.setLastName(REGISTRATION_LAST_NAME);
		registration.setSignInProvider(SOCIAL_SIGN_IN_PROVIDER);

		return registration;
	}
}

The first factory method has the following consequences:

  • The part of our test method, which creates the new RegistrationForm object, is a lot cleaner than before and the name of the factory method describes the state of the created RegistrationForm object.
  • The configuration of our mock object is harder to read because the value of the the email property is “hidden” inside our factory method.
  • Our assertions are harder to read because the property values of the created RegistrationForm object are “hidden” inside our factory method.
If we would use the object mother pattern, the problem would be even bigger because we would have to move the related constants to the object mother class.

I think that it is fair to say that even though the first factory method has its benefits, it has serious drawbacks as well.

Let’s see if the second factory method can eliminate those drawbacks.

The name of the second factory method is newRegistrationViaSocialSignIn(), and it takes the email address, first name, last name, and social sign in provider as method parameters. After we have added this factory method to our test class, the source of our unit test looks as follows (the relevant parts are highlighted):

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.runners.MockitoJUnitRunner;
import org.mockito.stubbing.Answer;
import org.springframework.security.crypto.password.PasswordEncoder;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.mockito.Matchers.isA;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;


@RunWith(MockitoJUnitRunner.class)
public class RepositoryUserServiceTest {

	private static final String REGISTRATION_EMAIL_ADDRESS = "john.smith@gmail.com";
	private static final String REGISTRATION_FIRST_NAME = "John";
	private static final String REGISTRATION_LAST_NAME = "Smith";
	private static final Role ROLE_REGISTERED_USER = Role.ROLE_USER;
	private static final SocialMediaService SOCIAL_SIGN_IN_PROVIDER = SocialMediaService.TWITTER;

	private RepositoryUserService registrationService;

	@Mock
	private PasswordEncoder passwordEncoder;

	@Mock
	private UserRepository repository;

	@Before
	public void setUp() {
		registrationService = new RepositoryUserService(passwordEncoder, repository);
	}


	@Test
	public void registerNewUserAccount_SocialSignInAndUniqueEmail_ShouldCreateNewUserAccountAndSetSignInProvider() throws DuplicateEmailException {
		RegistrationForm registration = newRegistrationViaSocialSignIn(REGISTRATION_EMAIL_ADDRESS,
																REGISTRATION_FIRST_NAME,
																REGISTRATION_LAST_NAME,
																SOCIAL_MEDIA_SERVICE
		);

		when(repository.findByEmail(REGISTRATION_EMAIL_ADDRESS)).thenReturn(null);

		when(repository.save(isA(User.class))).thenAnswer(new Answer<User>() {
			@Override
			public User answer(InvocationOnMock invocation) throws Throwable {
				Object[] arguments = invocation.getArguments();
				return (User) arguments[0];
			}
		});

		User createdUserAccount = registrationService.registerNewUserAccount(registration);

		assertEquals(REGISTRATION_EMAIL_ADDRESS, createdUserAccount.getEmail());
		assertEquals(REGISTRATION_FIRST_NAME, createdUserAccount.getFirstName());
		assertEquals(REGISTRATION_LAST_NAME, createdUserAccount.getLastName());
		assertEquals(SOCIAL_SIGN_IN_PROVIDER, createdUserAccount.getSignInProvider());
		assertEquals(ROLE_REGISTERED_USER, createdUserAccount.getRole());
		assertNull(createdUserAccount.getPassword());

		verify(repository, times(1)).findByEmail(REGISTRATION_EMAIL_ADDRESS);
		verify(repository, times(1)).save(createdUserAccount);
		verifyNoMoreInteractions(repository);
		verifyZeroInteractions(passwordEncoder);
	}
	
	private RegistrationForm newRegistrationViaSocialSignIn(String emailAddress, String firstName, String lastName, SocialMediaService signInProvider) {
		RegistrationForm registration = new RegistrationForm();
	
		registration.setEmail(emailAddress);
		registration.setFirstName(firstName);
		registration.setLastName(lastName);
		registration.setSignInProvider(signInProvider);

		return registration;
	}
}

The second factory method has the following consequences:

  • The part of our test method, which creates the new RegistrationForm object, is a bit messier than the same code which uses the first factory method. However, it is still cleaner than the original code because the name of the factory method describes the state of the created object.
  • It seems to eliminate the drawbacks of the first factory method because the property values of the created object are not “hidden” inside the factory method.

Seems cool, right?

It would be really easy to think that all is well in the paradise, but that is not the case. Although we have seen that factory methods can make our tests more readable, the thing is that they are a good choice only when the following conditions are met:

  1. The factory method doesn’t have too many method parameters. When the number of method parameter grows, our tests become harder to write and read. The obvious question is: how many method parameters a factory method can have? Unfortunately it is hard to give an exact answer to that question but I think that using a factory method is a good choice if the factory method has only a handful of method parameters.
  2. The test data doesn’t have too much variation. The problem of using factory methods is that a single factory method is typically suitable for one use case. If we need to support N use cases, we need to have N factory methods. This is a problem because over time our factory methods become bloated, messy, and hard to maintain (especially if we use the object mother pattern).

Let’s find out if test data builders can solve some of these problems.

Using Test Data Builders

A test data builder is a class which creates new objects by using the builder pattern. The builder pattern described in Effective Java has many benefits, but our primary motivation is to provide a fluent API for creating the objects used in our tests.

We can create a test data builder class which creates new RegistrationForm objects by following these steps:

  1. Create a RegistrationFormBuilder class.
  2. Add a RegistrationForm field to the created class. This field contains a reference to the created object.
  3. Add a default constructor to the created class and implement it by creating a new RegistrationForm object.
  4. Add methods which are used to set the property values of the created RegistrationForm object. Each method sets the property value by calling the correct setter method and returns a reference to the RegistrationFormBuilder object. Remember that the method names of these methods can either make or break our DSL.
  5. Add a build() method to the created class and implement it by returning the created RegistrationForm object.

The source code of our test data builder class looks as follows:

public class RegistrationFormBuilder {

    private RegistrationForm registration;

    public RegistrationFormBuilder() {
        registration = new RegistrationForm();
    }

    public RegistrationFormBuilder email(String email) {
        registration.setEmail(email);
        return this;
    }

    public RegistrationFormBuilder firstName(String firstName) {
        registration.setFirstName(firstName);
        return this;
    }

    public RegistrationFormBuilder lastName(String lastName) {
        registration.setLastName(lastName);
        return this;
    }

    public RegistrationFormBuilder isSocialSignInViaSignInProvider(SocialMediaService signInProvider) {
        registration.setSignInProvider(signInProvider);
        return this;
    }

    public RegistrationForm build() {
        return registration;
    }
}

After we have modified our unit test to use the new test data builder class, its source code looks as follows (The relevant part is highlighted):

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.runners.MockitoJUnitRunner;
import org.mockito.stubbing.Answer;
import org.springframework.security.crypto.password.PasswordEncoder;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.mockito.Matchers.isA;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;

@RunWith(MockitoJUnitRunner.class)
public class RepositoryUserServiceTest {

	private static final String REGISTRATION_EMAIL_ADDRESS = "john.smith@gmail.com";
	private static final String REGISTRATION_FIRST_NAME = "John";
	private static final String REGISTRATION_LAST_NAME = "Smith";
	private static final Role ROLE_REGISTERED_USER = Role.ROLE_USER;
	private static final SocialMediaService SOCIAL_SIGN_IN_PROVIDER = SocialMediaService.TWITTER;

	private RepositoryUserService registrationService;

	@Mock
	private PasswordEncoder passwordEncoder;

	@Mock
	private UserRepository repository;

	@Before
	public void setUp() {
		registrationService = new RepositoryUserService(passwordEncoder, repository);
	}


	@Test
	public void registerNewUserAccount_SocialSignInAndUniqueEmail_ShouldCreateNewUserAccountAndSetSignInProvider() throws DuplicateEmailException {
		RegistrationForm registration = new RegistrationFormBuilder()
			.email(REGISTRATION_EMAIL_ADDRESS)
			.firstName(REGISTRATION_FIRST_NAME)
			.lastName(REGISTRATION_LAST_NAME)
			.isSocialSignInViaSignInProvider(SOCIAL_SIGN_IN_PROVIDER)
			.build();

		when(repository.findByEmail(REGISTRATION_EMAIL_ADDRESS)).thenReturn(null);

		when(repository.save(isA(User.class))).thenAnswer(new Answer<User>() {
			@Override
			public User answer(InvocationOnMock invocation) throws Throwable {
				Object[] arguments = invocation.getArguments();
				return (User) arguments[0];
			}
		});

		User createdUserAccount = registrationService.registerNewUserAccount(registration);

		assertEquals(REGISTRATION_EMAIL_ADDRESS, createdUserAccount.getEmail());
		assertEquals(REGISTRATION_FIRST_NAME, createdUserAccount.getFirstName());
		assertEquals(REGISTRATION_LAST_NAME, createdUserAccount.getLastName());
		assertEquals(SOCIAL_SIGN_IN_PROVIDER, createdUserAccount.getSignInProvider());
		assertEquals(ROLE_REGISTERED_USER, createdUserAccount.getRole());
		assertNull(createdUserAccount.getPassword());

		verify(repository, times(1)).findByEmail(REGISTRATION_EMAIL_ADDRESS);
		verify(repository, times(1)).save(createdUserAccount);
		verifyNoMoreInteractions(repository);
		verifyZeroInteractions(passwordEncoder);
	}
}
If you want to learn more about fluent APIs, you should read the following articles:

As we can see, test data builders have the following benefits:

  • The code which creates new RegistrationForm objects is both easy to read and write. I am a big fan of fluent APIs, and I think that this code is both beautiful and elegant.
  • The builder pattern ensures that the variation found from our test data is no longer a problem because we can simply add new methods to the test data builder class.
  • The configuration of our mock object and our assertions are easy to read because the constants are visible in our test method and our DSL emphasizes the meaning of each property value.

So, should we use the builder pattern for everything?

NO!

We should use test data builders only when it makes sense. In other words, we should use them when

  1. We have set more than a handful of property values.
  2. Our test data has a lot of variation.

The builder pattern is a perfect choice if one of these conditions is true. The reason for this is that we can create a domain-specific language by naming the setter-like methods of the builder class. This makes our tests easy to read and write even if we would have create a lot of different objects and set a lot of property values.

That is the power of the builder patten.

That is all for today. Let’s move on and summarize what we learned from this blog post.

Summary

We learned why it is a bad idea to create objects in the test method by using the new keyword, and we learned two different ways to create the objects which are used in our tests.

To be more specific, this blog post has taught us three things:

  • It is a bad idea to create the required objects in the test method by using the new keyword because it makes our tests messy and hard to read.
  • If we have to set only a handful of property values and our test data doesn’t have a lot of variation, we should create the required object by using a factory method.
  • If we have to set a lot of property values and / or our test data has a lot of variation, we should create the required object by using a test data builder.
13 comments… add one
  • Rafał Jun 11, 2014 @ 10:59

    Hello,

    Good post!

    I have one remark though I would like to share. It regards Test Data Builders pattern.

    In your code the builder duplicates the code of RegistrationForm. RegistrationForm is not immutable object as it has setters that can modify its state after it is created. So in my opinion a builder is duplicating the code of RegistrationForm. Its only added value is improved readability. I have similar problem with my code. Builder was supposed to be used for creating objects that have many c-tor arguments. The links you refer to say this also.

    One of the possible solutions is to have something like that:

    
    class RegistrationForm {
       private String name;
       public void setName(String name) {
           this.name = name;
       }
       public RegistrationForm withName(String name) {
          setName(name);
          return this;
       }
    }
    
    

    This is like a bean and builder combined.

    I am a big fan of readable code, but creating builders for mutable object seems to me like a lot of duplication.

    • Petri Jun 11, 2014 @ 22:03

      Hi,

      Thank you for your interesting comment (once again).

      The decision to use test data builders is a tradeoff. You can write more readable tests by using test data builders, but you have to write to more code (and maintain this code). I think that tests should be as readable as possible because they are the only documentation which is always up-to-date.

      However, this doesn't mean that I would always use test data builders. I use factory methods when I need to create "simple" objects or highlight a certain property of an object. If I need to create "complex" objects, I use test data builders. This really works for me, and I have noticed that source code of my old tests looks pretty good as long as I have followed these guidelines. If not, well, let's just say that they aren't a pleasure to read.

      Also, I think that it is not a good idea to add "builder like" methods to your DTOs (or any other class) if you plan to use these methods only in your tests. I admit that it makes your tests easier to read but the price of this practice is high.

      It makes your source code harder to read because your classes are littered with methods which are invoked only by either unit or integration tests. I know that this a somewhat common practice, but I prefer to keep my production code and test code as separated as possible.

      • Rafał Jun 13, 2014 @ 15:52

        It is always a matter of choice.

        In my code I try to combine Object Mother, Factory Methods and Builders.

        Actually I spent lot of hours on investigating that issue. One of the things I found some time ago is Object Mother combined with Builders. Imagine, that Object Mother returns a Builder. You can then manipulate its state before you have a final object. It is a kind of a template. Look at the example below:

        
        /* Object Mother */
        public final class TestUsers { 
        
            public static UserBuilder aUser() {
                return UserBuilder.aUser()
                        .inUserRole()
                        .withName("John Smith")
                        .withUsername("jsmith")
                        .withPassword("42xyz");
            }
        
            public static UserBuilder anAdmin() {
                return UserBuilder.aUser()
                        .inAdminRole()
                        .withName("Chris Choke")
                        .withUsername("cchoke")
                        .withPassword("66abc");
            }
        }
        
        

        Then in test you can play with it like that (just an example):

        
        UserBuilder userBuilder = TestUsers.aUser();
        User aUser = userBuilder.but().withNoPassword().build()
        
        

        What do you think about this one?

        BTW. I found some good practices in Chapter 22, "Constructing Complex Test Data" of "Growing Object-Oriented Software, Guided by Tests" book.

        This subject is really awesome! We had a lot of discussions in my time around this topic as well.

        • Petri Jun 15, 2014 @ 11:37

          Hi,

          Again, thank you for such a great comment!

          I have noticed that most of the time software development is a matter of opinion. If I compare two different ways of doing something, often both of these ways have benefits and drawbacks. In end the, I have to choose which one is a better solution for my problem, and it would be a lie to say that my decision would always be objective.

          Your object mother class which uses builders looks very interesting because it makes the object mother easier to maintain even if your test data has a lot of variation (This has really caused me a lot of problems in the past).

          I think that this approach has the following benefits:

          • It makes the test methods shorter because you don't have to set the basic information of a user in every test method. Instead you can just set the information which is relevant for the test method.
          • The object mother class is easier to maintain because it creates the User by using the builder pattern.

          The only drawback which I can think of is that if you have to have write assertions for properties which contains the information set in the object mother class, it might not be obvious where the property value is set.

          Let's think about the following situation:

          
          TodoDTO addedTodo = ...
          UserBuilder userBuilder = TestUsers.aUser();
          User loggedInUser = userBuilder.build();
          
          Todo savedTodo = todoService.add(addedTodo, loggedInUser)
          
          assertThat(savedTodo.getCreatedBy()).isEqualTo("John Smith");
          
          

          The thing is that you have to specify the name of the user in two places. Now, I think that this is a minor problem and it can be solved by simply calling the getName() method of the User class in our test:

          
          TodoDTO addedTodo = ...
          UserBuilder userBuilder = TestUsers.aUser();
          User loggedInUser = userBuilder.build();
          
          Todo savedTodo = todoService.add(addedTodo, loggedInUser)
          
          assertThat(savedTodo.getCreatedBy()).isEqualTo(loggedInUser.getName());
          
          

          I think that looks a lot cleaner than its alternative (using the builder in the test):

          
          TodoDTO addedTodo = ...
          User loggedInUser = UserBuilder.aUser()
          			.inUserRole()
          			.withName("John Smith")
          			.withUsername("jsmith")
          			.withPassword("42xyz")
          			.build()
          
          Todo savedTodo = todoService.add(addedTodo, loggedInUser)
          
          assertThat(savedTodo.getCreatedBy()).isEqualTo(loggedInUser.getName());
          
          

          Now, one thing that still bothers me a bit is that if I build the RegistrationForm objects in this way, my assertions would look a bit messy. On the other hand, this is not such a big problem if I use custom assertions.

          At the moment I think that this approach is a great way to build "secondary objects" (like the one in my example). Also, I will have to think if this is a better way to build "primary objects" as well. It does definitely have its benefits (shorter test methods), but somehow I am stuck with the idea that the information of a "primary object" should declared in the test class by using constants. On the other hand, this approach makes it obvious that the information is "copied" from an object to another object (this isn't always obvious when you use constants).

          I guess I will have to try this approach in a real life project and see how it goes. Anyway, I really enjoy having these conversations!

          • Rafał Jun 15, 2014 @ 20:11

            Thanks for your reply. Basically, I agree with you remarks.

            I have one additional though.

            While thinking about the above, I am not sure if keeping a separate Object Mother is really necessary. We could easily move the methods from Object Mother directly to Test Data Builder:

            
            public class UserBuilder {
            
                public static final String DEFAULT_NAME = "John Smith";
                public static final String DEFAULT_ROLE = "ROLE_USER";
                public static final String DEFAULT_PASSWORD = "42";
            
                // field declarations omitted for readability
            
                private UserBuilder() {}
            
                public static UserBuilder aUser() {
                    return new UserBuilder();
                }
            
                public static UserBuilder aDefaultUser() {
                    return UserBuilder.aUser()
                            .withUsername("jsmith");
                }
            
                public static UserBuilder aUserWithNoPassword() {
                    return UserBuilder.aDefaultUser()
                            .withNoPassword();
                }
            
                public static UserBuilder anAdmin() {
                    return UserBuilder.aUser()
                            .inAdminRole();
                }
            
                // remaining methods omitted for readability
            
            }
            
            

            Thanks to that we can maintain the creation of User’s data inside a single class. Please note that I added some default values of properties that may be not relevant for most of the tests. But since they are defined as public constants they can be used in assertions, if we want so.

            Your article inspired me to write my own post. Please have a look here: http://blog.codeleak.pl/2014/06/test-data-builders-and-object-mother.html

          • Petri Jun 17, 2014 @ 20:18

            Hi,

            this looks interesting. To be honest, I cannot say which option (object mother + builder or methods in the builder class) looks better to me. Surprisingly, I like the object mother + builder a bit more but I cannot argue why this is the case.

            It is a bit weird because I agree that if you add those "factory methods" to the builder class, you can manage user's information in a single class. This is a definitely a benefit but I am a bit worried that the builder class might do "too much".

            Anyway, this is a really fun discussion! Also, I enjoyed reading your blog post. Good work!

      • Povilas Jun 23, 2016 @ 8:22

        Intresting post, thanks for a really deep look in the test data generation.

        One thing I wanted to mention is that you can have a Builder in the main code without the disadvantages you mention. The solution that I use is @Builder annotation from lombok (https://projectlombok.org/features/Builder.html). So, when I want to use Test Data Builder, I just add regular Builder on the class with @Builder. And just use the Builder.
        There is no problems with maintaining or refactoring it because everything is generated by lombok :)

        • Petri Jun 23, 2016 @ 23:28

          Hi,

          If you use Lombok and need to write data-centric code, you should definitely use the @Builder annotation. This is a good option for data-centric applications.

          That being said, if the created object can have multiple states and only one valid "start state", it is not a good idea to use a builder that allows developers to create objects which are not in the valid "start state". This is the situation where using test data builders make sense.

  • Joachim Van der Auwera Jul 2, 2014 @ 18:53

    I am not a fan of object mothers. They suggest using the same data in all tests, which is often not practical. The separation in a seperate class moves the exact data out of sight, which makes is worse.

    I prefer using the solution I explain at http://blog.progs.be/637/easier-mocks-with-data, which defines the data is a readable way (for now, only with mocks).

    Sample:

    
    Person person = FakerMock.withFields(Person.class,
    	"name", "John Doe",
    	"location", "Brussels",
    	"organization", FakerMock.withFields(Organization.class, 
    		"name", "Company Ltd"));
    
    
    • TWiStErRob Nov 30, 2014 @ 13:47

      While kind of agree with you, most of the time there's a lot of irrelevant data set up in tests. For example what the actual name or username is irrelevant if you're testing the interaction of the social provider, it just has to pass through: demonstrated in the `caling getName()` comment above.

      Also what about refactoring, speed and encapsulation?

  • Felipe Carvalho Sep 30, 2016 @ 13:34

    Hey, Petri, nice post!

    I like to use the same approach and even go one step further. Usually what I have is a test data builder that assigns random valid values to the attributes of the class being built. For instance, i use a random string for String attributes, random numbers, random enum values, and so on. And only invoke the -withSomething methods for specific situations, where I actually need to check for a specific value.

    In the examples you provided, my assertions would just check if both the form and the created account use the same vakues, instead of relying on constants for everything.

    I personally like this approach for the code looks smaller and cleaner in the end, besides the fact that it's not bound to specific values, enabling me to trust my tests a bit more. Besides, I don't necessarily need to invoke every test builder method to have a valid object, whenever I invoke build(), I always have a valid object.

    Thanks a lot for your posts, I really like going through them!

  • Jaehyun Sep 23, 2017 @ 14:53

    I just release a library about ObjectMother pattern. Please take a look. https://github.com/keepcosmos/beanmother

Leave a Reply