2

Assume the following POJO:

@Getter
@Setter
public class UserRequest {
    private String username;
    private String password;
    private String email;
}

Now I'd like to generate various test data for this class. Whenever some field is not set explicitly, a default value shall be set. Please bear in mind that I cannot add Lombok's @Builder or @Builder.Default to the original class or modify it in other ways.

So I came up with the following helper class:

public class UserRequestTestDataUtils {

    public static final String DEFAULT_USERNAME = "foo";
    public static final String DEFAULT_PASSWORD = "bar";
    public static final String DEFAULT_EMAIL = "foo@bar.com";

    public static UserRequest createUserRequestWithUsername(String username) {
        return createUserRequest(username, DEFAULT_PASSWORD, DEFAULT_EMAIL);
    }

    public static UserRequest createUserRequestWithPassword(String password) {
        return createUserRequest(DEFAULT_USERNAME, password, DEFAULT_EMAIL);
    }

    public static UserRequest createUserRequestWithEmail(String email) {
        return createUserRequest(DEFAULT_USERNAME, DEFAULT_PASSWORD, email);
    }

    public static UserRequest createUserRequestWithUsernameAndPassword(String username, String password) {
        return createUserRequest(username, password, DEFAULT_EMAIL);
    }

    public static UserRequest createUserRequest(String username, String password, String email) {
        final UserRequest userRequest = new UserRequest();
        userRequest.setUsername(username);
        userRequest.setPassword(password);
        userRequest.setEmail(email);    
        return userRequest;
    }
}

While this is ok for classes with only some fields, it gets tedious if there are more fields and for certain variations of the test data.

My first idea was to use Lombok's @Builder on the method level for createUserRequest():

@Builder
public static UserRequest createUserRequest(String username, String password, String email) {
    final UserRequest userRequest = new UserRequest();
    userRequest.setUsername(username);
    userRequest.setPassword(password);
    userRequest.setEmail(email);    
    return userRequest;
}

Which would allow the following:

UserRequestTestDataBuilder.builder()
  .username("foo")
  .password("bar")
  .build();

However, this doesn't set any default value for (in this case email). Is there some pattern which could simplify this approach?

Gautham M
  • 4,816
  • 3
  • 15
  • 37
Robert Strauch
  • 12,055
  • 24
  • 120
  • 192

2 Answers2

2

Why not just...

@Getter
@Setter
public class UserRequest {
    private String username = "";
    private String password = "";
    private String email = "(unknown email)";
}

If someone runs new UserRequest(), that will work, and that UR will have the default values. .set can be used to change the value of any or all of these fields.

DISCLAIMER: I'm one of the core contributors of lombok.

-- EDIT --

Aha, you can't change the original. If null is allowed to count as a sentinel (as in, as a signal that the default is required):

@Builder
public static UserRequest newUserRequest(String username, String password, String email) {
    UserRequest ur = new UserRequest();
    ur.setUsername(username == null ? DEFAULT_USERNAME : username);
    // etc
    return ur;
}

If instead you want the UR to get set up with certain defaults, but if someone explicitly does buildUserRequest().username(null).build() then, username should be null, oof. I'm not sure there is an easier way if you can't change the source of UserRequest.

rzwitserloot
  • 85,357
  • 5
  • 51
  • 72
  • While I have access to the `UserRequest` class, I'm not allowed to modify it. Thus, setting default values directly in the class is not an option. Also, the default values shall only apply to the objects for testing. I should make this more explicit in my question. – Robert Strauch Jun 18 '21 at 13:47
  • Yeah, your edit describes exactly what it's about incl. the "when null is passed, then set null" but I guess it's easier to provide dedicated helper methods for those cases. They are not that common. – Robert Strauch Jun 18 '21 at 15:57
2

You could actually achieve this with a little workaround in your util class, along with the method annotated with @Builder add the below class with the same name as the builder class that would be generated by lombok.

public class UserRequestTestDataUtils {
    // leave this method as such, without any null checks.
    @Builder
    public static UserRequest newUserRequest(String username, String password, String email) {
        UserRequest ur = new UserRequest();
        ur.setUsername(username);
        // etc
        return ur;
    }

    // Provide the default values here.
    public static class UserRequestBuilder {
        private String username = "foo";
        private String password = "bar";
        private String email = "foo@bar.com";
    } 
}    

Lombok would still generate the other required methods in the builder class as usual. You could override the behavior of any methods/fields within the lombok generated builder class like this. (Not sure if this is a right thing to do, but it works!)

NOTE : Passing an explicit null while building the object would set the value as null

//this would generate password and email with the given default values.
UserRequestTestDataUtils.builder().username("testname").build();
Gautham M
  • 4,816
  • 3
  • 15
  • 37
  • 1
    @RobertStrauch when you use `@Builder` on top of `newUserRequest` method, lombok generates an inner builder class. We kind of override it by creating a similar class of the "same name". But the trick is that not everything gets overridden. Rest of the code generated by lombok for the builder class remains same. – Gautham M Jun 18 '21 at 16:15
  • Great, I just had a typo in my field names. That seems to do the trick. But it would require to repeat all the fields from the original class, right? – Robert Strauch Jun 18 '21 at 16:17
  • 1
    @RobertStrauch No. Not for all fields. Only those for which you need default values. – Gautham M Jun 18 '21 at 16:18
  • That seems to solve my issue. Guess I'll also try the variant described by @rzwitserloot and then decide which fits better :-) – Robert Strauch Jun 18 '21 at 16:20
  • 1
    @RobertStrauch Yes with that approach you need not write this builder class. I think the above approach may come in handy in these scenarios as well-> https://stackoverflow.com/a/67401875/7804477 – Gautham M Jun 18 '21 at 16:27