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?
Enable JpaAudit In order to enable JPA Auditing for this project will need to apply three annotations and a configuration class. Those annotations are; @EntityListener , @CreatedDate , and @LastModifiedDate . @EntityListener will be the one that is responsible to listen to any create or update activity.
Auditing basically involves tracking and logging every change we make to our persisted data, which consists of tracking and storing every insert, update, and delete activity. Auditing aids in the preservation of history records, which can later be used to trace user activity.
@DataJpaTest is used to test JPA repositories. It is used in combination with @RunWith(SpringRunner. class) . The annotation disables full auto-configuration and applies only configuration relevant to JPA tests. By default, tests annotated with @DataJpaTest use an embedded in-memory database.
All that you have to do is annotate your persistent class or some of its properties, that you want to audit, with @Audited . For each audited entity, a table will be created, which will hold the history of changes made to the entity. You can then retrieve and query historical data without much effort.
@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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With