ketrab321
ketrab321

Reputation: 591

Test configuration for Jpa auditing

I am trying to use Spring Data, Hibernate Envers and auditing in Spring Boot application. I have configured AuditorAwareImpl

public class AuditorAwareImpl implements AuditorAware<String> {

    @Override
    public Optional<String> getCurrentAuditor() {
        return Optional.of("Default auditor");
    }
}

and configuration class for it.

@Configuration
@EnableJpaAuditing(auditorAwareRef = "auditorProvider")
public class AuditingConfiguration {

    @Bean
    public AuditorAware<String> auditorProvider() {
        return new AuditorAwareImpl();
    }
}

Now I would like to create AuditorAware for my Integration tests. I have created new configuration class with test auditor

@Configuration
@Profile("test")
@EnableJpaAuditing(auditorAwareRef = "testAuditorProvider")
public class TestAuditingConfiguration {

    @Bean
    @Primary
    public AuditorAware<String> testAuditorProvider() {
        return () -> Optional.of("Test auditor");
    }

}

And when I try to write my integration test like this

@RunWith(SpringRunner.class)
@SpringBootTest
@ActiveProfiles("test")
public class AuditingApplicationTests {

    @Autowired
    private AuditorAware<String> auditorAware;

    @Autowired
    private MovieRepository movieRepository;

    @Test
    public void testCurrentAuditor() {
        String currentAuditor = auditorAware.getCurrentAuditor().get();
        assertEquals("Test auditor", currentAuditor);
    }

    @Test
    public void movieRepositoryTest() {
        Movie movie = new Movie("Movie");
        movieRepository.save(movie);

        List<Movie> movies = movieRepository.findAll();
        Movie result = movies.get(0);
        assertEquals("Test auditor", result.getCreatedBy());
    }
}

I am getting this error:

Caused by: org.springframework.beans.factory.support.BeanDefinitionOverrideException: Invalid bean definition with name 'jpaAuditingHandler' defined in null: Cannot register bean definition [Root bean: class [org.springframework.data.auditing.AuditingHandler]; scope=; abstract=false; lazyInit=false; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null] for bean 'jpaAuditingHandler': There is already [Root bean: class [org.springframework.data.auditing.AuditingHandler]; scope=; abstract=false; lazyInit=false; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null] bound.

When I remove @EnableJpaAuditing from TestAuditingConfiguration it works fine with one exception - autowired auditorAware in test is one from TestAuditingConfiguration but on the other hand for auditing is used from AuditingConfiguration so result.getCreatedBy() will return Default auditor instead of Test auditor. I read that for database integration tests should be used @DataJpaTest annotation so I have changed it. Now with enabled @EnableJpaAuditing on TestAuditingConfiguration I received:

org.springframework.beans.factory.UnsatisfiedDependencyException:  Unsatisfied dependency expressed through field 'auditorAware'; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'org.springframework.data.domain.AuditorAware<java.lang.String>' available: expected at least 1 bean which qualifies as autowire candidate

But after adding @Import(TestAuditingConfiguration.class) it works as I excpected - result.getCreatedBy() returns Test auditor. So finally my test class looks like:

@RunWith(SpringRunner.class)
@DataJpaTest
@ActiveProfiles("test")
@Import(TestAuditingConfiguration.class)
public class AuditingApplicationTests {

    @Autowired
    private AuditorAware<String> auditorAware;

    @Autowired
    private MovieRepository movieRepository;

    @Test
    public void testCurrentAuditor() {
        String currentAuditor = auditorAware.getCurrentAuditor().get();
        assertEquals("Test auditor", currentAuditor);
    }

    @Test
    public void movieRepositoryTest() {
        Movie movie = new Movie("Movie");
        movieRepository.save(movie);

        List<Movie> movies = movieRepository.findAll();
        Movie result = movies.get(0);
        assertEquals("Test auditor", result.getCreatedBy());
    }

}

Now I am really confused how beans are used in specific profiles and how @SpringBootTest and @DataJpaTest works. And why @Import was important for @DataJpaTest? Can anyone explain me that and what is preferred approach for database testing?

Upvotes: 16

Views: 11203

Answers (3)

ab1207
ab1207

Reputation: 85

Try extending the AuditorAwareImpl and create a bean using that class. This way you can prevent using multiple @EnableJpaAuditing (which won't work if you don't allow overriding bean).

The way I got it working was with the following setup:

public class TestAuditingConfiguration {

  @Bean
  @Primary
  AuditorAwareImpl auditorProvider() {
    return new TestAuditorAware();
  }

  public static class TestAuditorAware extends AuditorAwareImpl {

    @Override
    public Optional<String> getCurrentAuditor() {
      return Optional.of("test");
    }
  }
}

And use it in your Spring test class with: @Import(TestAuditingConfiguration.class)

Upvotes: 0

Andrea Ciccotta
Andrea Ciccotta

Reputation: 672

move @EnableJpaAuditing on another new file

import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;

@Configuration
@EnableJpaAuditing
public class JpaConfiguration {
}

Upvotes: 2

Jens Schauder
Jens Schauder

Reputation: 81970

@DataJpaTest is just a shortcut for a bunch of annotations. See https://docs.spring.io/spring-boot/docs/current/api/org/springframework/boot/test/autoconfigure/orm/jpa/DataJpaTest.html

It basically creates an ApplicationContext with only those beans regsitered that are relevant for JPA and Spring Data JPA.

@SpringBootTest creates a complete ApplicationContext with everything that is in your application. A lot of which it finds by scanning the class path for classes annotated with @Configuration.

Therefore it will contain more "stuff" and in your case two @AuditorAware beans. Which some "magic" inside Spring tries to create a jpaAuditingHandler bean from. Since there are two AuditorAware beans we end up with two beans of same name which is not acceptable. See Spring boot 2.1 bean override vs. Primary. You Probably could enable bean overriding but that is not recommended.

With @DataJpaTest these configurations are not found and you end up with no AuditorAware bean at all. By importing the right configuration you get exactly the bean you want for your test and everything is happy.

Which one is the better approach depends on how you run your tests and in the end is probably mostly a matter of taste.

I prefer using @DataJpaTest for real systems, because it limits the effect of unrelated configurations on the database test. It might also execute slightly faster although that is probably hardly noticeable for most test suites because in most applications I work with, most of the startup time was eaten by Hibernate which is required for database tests anyway and when you run multiple test having different configurations for different tests negatively affects caching.

For small projects I prefer @SpringBootTest since it is less to write and makes things simpler at least in the simple cases.

Upvotes: 2

Related Questions