0

I'm porting an app across from JDBC / REST to spring-data-rest and my one and only unit test with fails with the error

NoSuchBeanDefinitionException: 
    No qualifying bean of type 'com.xxx.repository.ForecastRepository' available

The app was retro-fitted with spring-boot just previously, and now I'm trying to put a new layer in place with spring-data-rest on top of spring-data-jpa.

I'm attempting to work out the correct Java-config according to

Custom Test Slice with Spring Boot 1.4

but I had to deviate from the idiomatic approach because

  1. the @WebMvcTest annotation doesn't suppress the security module which causes the test to fail
  2. the @MockMvcAutoConfiguration fails due to missing dependencies unless I specify @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK) (see here)
  3. @WebMvcTest and @SpringBootTest are mutually exclusive since they both specify @BootstrapWith and can't run together

So this is the closest I've got but Spring can't locate my @RepositoryRestResource repository:

Repository

@RepositoryRestResource(collectionResourceRel = "forecasts", path = "forecasts")
public interface ForecastRepository extends CrudRepository<ForecastExEncoded,
        Long> {

JUnit Test

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK,
        classes = {TestRestConfiguration.class})
public class ForecastRestTests {

    @Autowired
    private MockMvc mockMvc;

    @Autowired
    private ForecastRepository forecastRepository;

    @Before
    public void deleteAllBeforeTests() throws Exception {
        forecastRepository.deleteAll();
    }

    @Test
    public void shouldReturnRepositoryIndex() throws Exception {

        mockMvc.perform(get("/")).andDo(print()).andExpect(status().isOk()).andExpect(
                jsonPath("$._links.forecasts").exists());
    }

}

Configuration

@OverrideAutoConfiguration(enabled = false)
@ImportAutoConfiguration(value = {
        RepositoryRestMvcAutoConfiguration.class,
        HttpMessageConvertersAutoConfiguration.class,
        WebMvcAutoConfiguration.class,
        MockMvcAutoConfiguration.class,
        MockMvcSecurityAutoConfiguration.class
})
@Import({PropertySpringConfig.class})
public class TestRestConfiguration {}

Also tried...

I tried to configure the unit test with just @WebMvcTest and this @ComponentScan below from How to exclude AutoConfiguration from Spring Boot), in an attempt to simplify it all, however the excludeFilters had no effect.

@ComponentScan(
        basePackages="com.xxx",
        excludeFilters = {
                @ComponentScan.Filter(type = ASSIGNABLE_TYPE,
                        value = {
                                SpringBootWebApplication.class,
                                JpaDataConfiguration.class,
                                SecurityConfig.class
                        })
        })

I've set Spring's logging to trace because all I can do at this point is try to find clues as to what is happening from log output. So far though without any luck.

I can see in the logging that RepositoryRestConfiguration is loading, but obviously it isn't fed with the right info and I am unable to work out how that is done, after googling and pouring over the Spring docs and API. I think I must have read every relevant question here on SO .

Update 2016-11-16 10:00

One thing I see in the logs which concerns me is this:

Performing dependency injection for test context  [DefaultTestContext@2b4a2ec7 [snip...]
 classes = '{class com.xxx.TestRestConfiguration, 
    class com.xxx.TestRestConfiguration}', 

i.e. the context lists the configuration class twice. I specified the config class (once only) on the @SpringBootTest#classes annotation. But if I leave off the #classes from the annotation, Spring Boot finds and pulls in all the config via the @SpringBootApplication class.

So is that a hint that I am specifying the configuration in the wrong place? How else would I do it?

Community
  • 1
  • 1
Adam
  • 5,215
  • 5
  • 51
  • 90

1 Answers1

1

After way too much time, I settled on this approach.

Custom Test Slice with Spring Boot 1.4 looked promising but I couldn't get anywhere with it.

While going over and over

Accessing JPA Data with REST

I realised I had to include the JPA setup because spring-data-rest is using them directly - no chance to mock them or run unit tests without an embedded database.

At least not as far as I understand it. Maybe it is possible to mock them and have spring-data-rest run on the mocks against test data, but I think spring-data-rest and spring-data are probably too tightly coupled.

So integration testing it must be.

In the Spring source code provided with the articles above

gs-accessing-data-rest/ApplicationTests.java

the logging shows Spring Boot pulling in the whole configuration for the application context.

So that my SpringBootApplication class is avoided and the security module isn't loaded up, I set up my tests like this:

@RunWith(SpringRunner.class)
@SpringBootTest
@ContextConfiguration(classes = {
        JpaDataConfiguration.class,
        TestJpaConfiguration.class,
        TestRestConfiguration.class,
        PropertySpringConfig.class})
public class ForecastRestTests {

    @SuppressWarnings("SpringJavaAutowiringInspection")
    @Autowired
    private MockMvc mockMvc;

    @Autowired
    private ForecastRepository forecastRepository;

    @Before
    public void deleteAllBeforeTests() throws Exception {
        forecastRepository.deleteAll();
    }

    @Test
    public void shouldReturnRepositoryIndex() throws Exception {

        mockMvc.perform(get("/")).andDo(print()).andExpect(status().isOk()).andExpect(
                jsonPath("$._links.forecasts").exists());
    }

}

with these configuration classes:

@Configuration
@EnableJpaRepositories(basePackages = {"com.bp.gis.tardis.repository"})
@EntityScan(basePackages = {"com.bp.gis.tardis.type"})
public class JpaDataConfiguration {

and

@Configuration
@OverrideAutoConfiguration(enabled = false)
@ImportAutoConfiguration(value = {
        CacheAutoConfiguration.class,
        JpaRepositoriesAutoConfiguration.class,
        DataSourceAutoConfiguration.class,
        DataSourceTransactionManagerAutoConfiguration.class,
        HibernateJpaAutoConfiguration.class,
        TransactionAutoConfiguration.class,
        TestDatabaseAutoConfiguration.class,
        TestEntityManagerAutoConfiguration.class })
public class TestJpaConfiguration {}

and

@Configuration
@OverrideAutoConfiguration(enabled = false)
@ImportAutoConfiguration(value = {
        RepositoryRestMvcAutoConfiguration.class,
        HttpMessageConvertersAutoConfiguration.class,
        WebMvcAutoConfiguration.class,
        MockMvcAutoConfiguration.class,
        MockMvcSecurityAutoConfiguration.class
})
public class TestRestConfiguration {}

so the TL;DR summary is: use @ContextConfiguration to specify the configuration files that specify @OverrideAutoConfiguration and @ImportAutoConfiguration

Adam
  • 5,215
  • 5
  • 51
  • 90