Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 757
bartoszsokolik Avatar asked May 26 '19 21:05

bartoszsokolik


People also ask

How do I enable JPA auditing?

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.

What is auditing in JPA?

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.

What is data JPA test?

@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.

What is the use of @audited annotation?

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.


1 Answers

@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.

like image 94
Jens Schauder Avatar answered Sep 17 '22 14:09

Jens Schauder