Getting “At least one JPA metamodel must be presen

2019-03-18 02:47发布

问题:

I'm fairly new to Spring, trying to do some basic integration tests for a @Controller.

@RunWith(SpringRunner.class)
@WebMvcTest(DemoController.class)
public class DemoControllerIntegrationTests {
    @Autowired
    private MockMvc mvc;

    @MockBean
    private DemoService demoService;

    @Test
    public void index_shouldBeSuccessful() throws Exception {
        mvc.perform(get("/home").accept(MediaType.TEXT_HTML)).andExpect(status().isOk());
    }
}

but I'm getting

java.lang.IllegalStateException: Failed to load ApplicationContext
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'jpaMappingContext': Invocation of init method failed; nested exception is java.lang.IllegalArgumentException: At least one JPA metamodel must be present!
Caused by: java.lang.IllegalArgumentException: At least one JPA metamodel must be present!

Unlike most people posting this error, I don't want to use JPA for this. Am I trying to use @WebMvcTest incorrectly? How can I track down the Spring magic that's inviting JPA to this party?

回答1:

Remove any @EnableJpaRepositories or @EntityScan from your SpringBootApplication class instead do this:

package com.tdk;

@SpringBootApplication
@Import({ApplicationConfig.class })
public class TdkApplication {

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

And put it in a separate config class:

package com.tdk.config;

@Configuration
@EnableJpaRepositories(basePackages = "com.tdk.repositories")
@EntityScan(basePackages = "com.tdk.domain")
@EnableTransactionManagement
public class ApplicationConfig {

}

And here the tests:

@RunWith(SpringRunner.class)
@WebAppConfiguration
@WebMvcTest
public class MockMvcTests {

}


回答2:

I had the same problem. @WebMvcTest looks for a class annotated with @SpringBootApplication (in the same directory or higher up in your app structure if it doesn't find one). You can read how this works @ https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#boot-features-testing-spring-boot-applications-testing-autoconfigured-mvc-tests.

If your class annotated with @SpringBootApplication also has @EntityScan /@EnableJpaRepositories this error occurs. Because you have these annotations with @SpringBootApplication and you are mocking the service ( so actually not using any JPA ). I found a workaround which may not be the prettiest, but works for me.

Place this class in your test directory ( the root ). @WebMvcTest will find this class before your actual Application class. In this class you don't have to add @EnableJpaRepositories/@EntityScan.

@SpringBootApplication(scanBasePackageClasses = {
    xxx.service.PackageMarker.class,
    xxx.web.PackageMarker.class
})
public class Application {
}

And your test will look the same.

@RunWith(SpringRunner.class)
@WebMvcTest
@WithMockUser
public class ControllerIT {

    @Autowired
    private MockMvc mockMvc;

    @MockBean
    private Service service;

    @Test
    public void testName() throws Exception {
        // when(service.xxx(any(xxx.class))).thenReturn(xxx); 
        // mockMvc.perform(post("/api/xxx")...
        // some assertions
    }
}

Hope this helps!



回答3:

Alternatively, you can define a custom configuration class inside your test case, including only the controller (plus it's dependencies), to force Spring to use this context.
Please note, you'll still have access to MockMvc and other goodness in your test case, if it's WebMvcTest annotated.

@RunWith(SpringRunner.class)
@WebMvcTest(DemoController.class)
public class DemoControllerIntegrationTests {
    @Autowired
    private MockMvc mvc;

    @MockBean
    private DemoService demoService;

    @Test
    public void index_shouldBeSuccessful() throws Exception {
        mvc.perform(get("/home").accept(MediaType.TEXT_HTML)).andExpect(status().isOk());
    }

    @Configuration
    @ComponentScan(basePackageClasses = { DemoController.class })
    public static class TestConf {}


回答4:

If anyone uses Spring boot and don't want to remove @EntityScan and @EnableJpaRepositories you can remove @WebMvcTest annotation from your test class and add the following instead:

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@AutoConfigureMockMvc
public class DemoControllerIntegrationTests {
    @Autowired
    private MockMvc mvc;

    //...
}

and you will be able to autowire MockMvc and use it.