0

Let's take the following classes which are a simplification of more complex classes and their relationships.

@Data
@Builder
public class UserAccount {
    private String username;
    private String password;
    private Language contactLanguage;
    
    public static UserAccount.UserAccountBuilder defaultUserAccount() {
        return UserAccount.builder()
                .username("default_username")
                .password("default_password")
                .contactLanguage(defaultLanguage().build());
    } 
}

@Data
@Builder
public class Language {
    private String name;
    private String iso2;
    private String iso3;

    public static Language.LanguageBuilder defaultLanguage() {
        return Language.builder()
                .name("default_language_name")
                .iso2("default_iso2")
                .iso3("default_iso3");
    }
}

Using Lombok's @Builder annotation, I can easily construct an object like this, especially for testing:

UserAccount.builder()
  .username("foo")
  .password("bar")
  .contactLanguage(Language.builder()
    .name("English")
    .iso2("EN")
    .iso3("ENG")
    .build())
  .build();

// Or even like this...

defaultUserAccount().build();

This works fine for unit tests or any tests where such generated objects are only required to exist in memory.

However I'd also like to use this approach for integration tests with an underlying database (using Spring Boot 2.4 + JPA + Hibernate). And this is where some issues come up I couldn't solve so far. Let's have a look:

Each UserAccount needs to have a contactLanguage, but Language lives on its own. Other entities might use it as well. When constructing a user account with defaultUserAccount().build(), then persisting this entity fails because the Language object has not been persisted yet. There is no persist cascade on contactLanguage because I don't want "any" Language being created upon creating a UserAccount.

My only idea would be to use defaultLanguage().build() and persist this before defaultUserAccount().build(). But I feel that this will become complex and flaky as soon as there are more levels of nested builders or relationship to other entites.

Another thing is: Even if I managed to persist the defaultLanguge, I would run into a collision as soon as another test calls defaultUserAccount().build() because then the langauge already exists and cannot be inserted again.

Are there any patterns or approaches for persisting such test data objects?


Update #1

After more searching, I found this question on SO which looks almost identical.

Robert Strauch
  • 12,055
  • 24
  • 120
  • 192
  • so in your database you have already a table "Language" where you store the languages that will be associated with the `UserAccount` you want to insert? – Alberto Sinigaglia Jun 23 '21 at 23:06
  • @AlbertoSinigaglia No, the database schema is created on-the-fly only for the time of the running tests. So there is no "pre populated" database or tables. – Robert Strauch Jun 24 '21 at 07:36
  • but then you want your UserAccount to be associated to a Language, but the language can't be inserted before the UserAccount, but the UserAccount has a contain with the Language that does not allow the insert without a language associated.... – Alberto Sinigaglia Jun 24 '21 at 14:21
  • Well, technically I could insert the language at "some" point and then assign it to the user account. The question is: Where would that happen? I can think of some setup method like `@BeforeEach` or `@BeforeAll` but this also will become quite complex when objects and relations grow. I thought that there maybe is some Java pattern which could simplify this. – Robert Strauch Jun 24 '21 at 14:31
  • After some more searching I found this question on SO which actually describes my issue pretty well: https://stackoverflow.com/questions/1471479/persisting-complex-test-data – Robert Strauch Jun 24 '21 at 14:47

0 Answers0