简体   繁体   中英

Configuring Spring's @DataJpaTest that overrides default application context beans

I'm struggling with configuration for my @DataJpaTest. I'd like to take advantage of auto-configured spring context provided by @DataJpaTest, but I'd like to override some of it's beans.

This is my main class:

public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }

    @Bean
    public CommandLineRunner commandLineRunner(BookInputPort bookInputPort) {
        return args -> {
            bookInputPort.addNewBook(new BookDto("ABC", "DEF"));
            bookInputPort.addNewBook(new BookDto("GHI", "JKL"));
            bookInputPort.addNewBook(new BookDto("MNO", "PRS"));
        };
    }

As you can clearly see, I provide my implementation for CommandLineRunner that depends on some service.

I also have a test:

@DataJpaTest
public class BookRepositoryTest {

    public static final String TITLE = "For whom the bell tolls";
    public static final String AUTHOR = "Hemingway";

    @Autowired
    private BookRepository bookRepository;

    @Test
    public void testRepository() {
        Book save = bookRepository.save(new Book(TITLE, AUTHOR));
        assertEquals(TITLE, save.getTitle());
        assertEquals(AUTHOR, save.getAuthor());
    }
}

When I run the test I get the following error:

No qualifying bean of type 'com.example.demo.domain.book.ports.BookInputPort' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}

That makes perfect sense. Auto-configured test provides implementation only for a 'slice' of the context. Apparently implementation of BookInputPort is missing. I do not need this commandLineRunner in a test context. I get create a commandLineRunner that does not depend on any service: I can try to solve the issue by adding to my test class nested class :

@TestConfiguration
    static class BookRepositoryTestConfiguration {

        @Bean
        CommandLineRunner commandLineRunner() {
            return args -> {
            };
        }
    }

That solves the problem. Kind of. If I had more tests like this I would have to copy-paste this nested class to each test class. It's not an optimal solution. I tried to externalize this to a configuration that could be imported by @Import This is the config class:

@Configuration
public class MyTestConfiguration {

    @Bean
    public CommandLineRunner commandLineRunner() {
        return args -> {
        };
    }
}

But then the application fails with a message:

Invalid bean definition with name 'commandLineRunner' defined in com.example.demo.DemoApplication: Cannot register bean definition

I checked that error and other folks on SO suggested in this case:

@DataJpaTest(properties = "spring.main.allow-bean-definition-overriding=true")

I did that and I got:

No qualifying bean of type 'com.example.demo.domain.book.ports.BookInputPort' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}

That is exactly the same problem I started with. I took all these steps and found myself in the place where I was at the very beginning.

Do you have any idea how to solve this issue? I do not have a vague idea or clue.

@DataJpaTest generally starts scanning from current package of the test class and scans upwards till it finds the class annotated with @SpringBootConfiguration.

So creating a SpringBootConfiguration class at the repository root package will create only beans defined within that package. On top of that we can add any customized test bean required for our test class in that configuration class.

@SpringBootConfiguration
@EnableAutoConfiguration
public class TestRepositoryConfig {

    @Bean
    public CommandLineRunner commandLineRunner() {
        return args -> {
        };
    }

    @Bean
    public BookInputPort bootInputPort(){
        return new BookInputPort();
    }

}

In Spring Boot test class you can do it like this

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
public class YourClassTest {

    @Autowired
    private YourService yourService;

It does load all the services and I am using it perfectly fine

How to load specific entities in one environment and not the other

  1. You can use @Profile annotation in your entities.
  2. Put @Profile({"prod"}) on entities/services you don't want to load in test run, and
  3. Put @Profile({"prod", "test"}) on entities/services you want to load in both test and prod environments
  4. Then run the test with test profile. It will not load unnecessary entities .
  5. You can put @Profile annotation on other services too.

The annotation @DataJpaTest only loads the JPA part of a Spring Boot application, the application context might not loaded because you have @DataJpaTest annotation. Try to replace the @DataJpaTest with @SpringBootTest .

As per Spring documentation :

By default, tests annotated with @DataJpaTest will use an embedded in-memory database (replacing any explicit or usually auto-configured DataSource). The @AutoConfigureTestDatabase annotation can be used to override these settings. If you are looking to load your full application configuration, but use an embedded database, you should consider @SpringBootTest combined with @AutoConfigureTestDatabase rather than this annotation.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM