Reputation: 591
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
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
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
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