Spring Boot Data JPA无法在MockMVC测试中自动装配存储库接口

时间:2017-02-26 18:32:38

标签: java spring spring-boot spring-data-jpa

我正在使用Spring Data JPA和Spring Boot,并进行模拟MVC测试,结​​果是以下堆栈跟踪:

Parameter 1 of constructor in xyz.jacobclark.controllers.HomeController required a bean of type 'xyz.jacobclark.repositories.PageRepository' that could not be found.


Action:

Consider defining a bean of type 'xyz.jacobclark.repositories.PageRepository' in your configuration.

2017-02-26 18:29:11.350 ERROR 65426 --- [           main] o.s.test.context.TestContextManager      : Caught exception while allowing TestExecutionListener [org.springframework.boot.test.autoconfigure.SpringBootDependencyInjectionTestExecutionListener@7219ec67] to prepare test instance [xyz.jacobclark.controllers.HomeControllerTest@1f0f1111]

java.lang.IllegalStateException: Failed to load ApplicationContext
    at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:124) ~[spring-test-4.3.6.RELEASE.jar:4.3.6.RELEASE]
    at org.springframework.test.context.support.DefaultTestContext.getApplicationContext(DefaultTestContext.java:83) ~[spring-test-4.3.6.RELEASE.jar:4.3.6.RELEASE]
    at org.springframework.boot.test.autoconfigure.SpringBootDependencyInjectionTestExecutionListener.prepareTestInstance(SpringBootDependencyInjectionTestExecutionListener.java:47) ~[spring-boot-test-autoconfigure-1.5.1.RELEASE.jar:1.5.1.RELEASE]
    at org.springframework.test.context.TestContextManager.prepareTestInstance(TestContextManager.java:230) ~[spring-test-4.3.6.RELEASE.jar:4.3.6.RELEASE]
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.createTest(SpringJUnit4ClassRunner.java:228) [spring-test-4.3.6.RELEASE.jar:4.3.6.RELEASE]
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner$1.runReflectiveCall(SpringJUnit4ClassRunner.java:287) [spring-test-4.3.6.RELEASE.jar:4.3.6.RELEASE]
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) [junit-4.12.jar:4.12]
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.methodBlock(SpringJUnit4ClassRunner.java:289) [spring-test-4.3.6.RELEASE.jar:4.3.6.RELEASE]
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:247) [spring-test-4.3.6.RELEASE.jar:4.3.6.RELEASE]
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:94) [spring-test-4.3.6.RELEASE.jar:4.3.6.RELEASE]
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290) [junit-4.12.jar:4.12]
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71) [junit-4.12.jar:4.12]
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288) [junit-4.12.jar:4.12]
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58) [junit-4.12.jar:4.12]
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268) [junit-4.12.jar:4.12]
    at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61) [spring-test-4.3.6.RELEASE.jar:4.3.6.RELEASE]
    at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70) [spring-test-4.3.6.RELEASE.jar:4.3.6.RELEASE]
    at org.junit.runners.ParentRunner.run(ParentRunner.java:363) [junit-4.12.jar:4.12]
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:191) [spring-test-4.3.6.RELEASE.jar:4.3.6.RELEASE]
    at org.junit.runner.JUnitCore.run(JUnitCore.java:137) [junit-4.12.jar:4.12]
    at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:117) [junit-rt.jar:na]
    at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:42) [junit-rt.jar:na]
    at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:262) [junit-rt.jar:na]
    at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:84) [junit-rt.jar:na]
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0]
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0]
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0]
    at java.lang.reflect.Method.invoke(Method.java:483) ~[na:1.8.0]
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:147) [idea_rt.jar:na]
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'homeController' defined in file [/Users/jacobclark/workspace/jacobclark.xyz/build/classes/main/xyz/jacobclark/controllers/HomeController.class]: Unsatisfied dependency expressed through constructor parameter 1; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'xyz.jacobclark.repositories.PageRepository' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}
    at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:749) ~[spring-beans-4.3.6.RELEASE.jar:4.3.6.RELEASE]
    at org.springframework.beans.factory.support.ConstructorResolver.autowireConstructor(ConstructorResolver.java:189) ~[spring-beans-4.3.6.RELEASE.jar:4.3.6.RELEASE]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireConstructor(AbstractAutowireCapableBeanFactory.java:1193) ~[spring-beans-4.3.6.RELEASE.jar:4.3.6.RELEASE]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1095) ~[spring-beans-4.3.6.RELEASE.jar:4.3.6.RELEASE]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:513) ~[spring-beans-4.3.6.RELEASE.jar:4.3.6.RELEASE]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:483) ~[spring-beans-4.3.6.RELEASE.jar:4.3.6.RELEASE]
    at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:306) ~[spring-beans-4.3.6.RELEASE.jar:4.3.6.RELEASE]
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230) ~[spring-beans-4.3.6.RELEASE.jar:4.3.6.RELEASE]
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:302) ~[spring-beans-4.3.6.RELEASE.jar:4.3.6.RELEASE]
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:197) ~[spring-beans-4.3.6.RELEASE.jar:4.3.6.RELEASE]
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:761) ~[spring-beans-4.3.6.RELEASE.jar:4.3.6.RELEASE]
    at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:866) ~[spring-context-4.3.6.RELEASE.jar:4.3.6.RELEASE]
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:542) ~[spring-context-4.3.6.RELEASE.jar:4.3.6.RELEASE]
    at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:737) ~[spring-boot-1.5.1.RELEASE.jar:1.5.1.RELEASE]
    at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:370) ~[spring-boot-1.5.1.RELEASE.jar:1.5.1.RELEASE]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:314) ~[spring-boot-1.5.1.RELEASE.jar:1.5.1.RELEASE]
    at org.springframework.boot.test.context.SpringBootContextLoader.loadContext(SpringBootContextLoader.java:120) ~[spring-boot-test-1.5.1.RELEASE.jar:1.5.1.RELEASE]
    at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContextInternal(DefaultCacheAwareContextLoaderDelegate.java:98) ~[spring-test-4.3.6.RELEASE.jar:4.3.6.RELEASE]
    at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:116) ~[spring-test-4.3.6.RELEASE.jar:4.3.6.RELEASE]
    ... 28 common frames omitted
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'xyz.jacobclark.repositories.PageRepository' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.raiseNoMatchingBeanFound(DefaultListableBeanFactory.java:1486) ~[spring-beans-4.3.6.RELEASE.jar:4.3.6.RELEASE]
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1104) ~[spring-beans-4.3.6.RELEASE.jar:4.3.6.RELEASE]
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1066) ~[spring-beans-4.3.6.RELEASE.jar:4.3.6.RELEASE]
    at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:835) ~[spring-beans-4.3.6.RELEASE.jar:4.3.6.RELEASE]
    at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:741) ~[spring-beans-4.3.6.RELEASE.jar:4.3.6.RELEASE]

这是实际的存储库:

package xyz.jacobclark.repositories;

import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;
import xyz.jacobclark.models.entities.Page;

import java.util.List;

@Repository
public interface PageRepository extends CrudRepository<Page, String> {
    List<Page> findById(String id);

    List<Page> findByTitle(String titlex);
}

虽然控制器看起来像:

package xyz.jacobclark.controllers;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import xyz.jacobclark.adapters.GitHubAdapter;
import xyz.jacobclark.repositories.PageRepository;

import java.util.Map;
import java.util.concurrent.ExecutionException;

@Controller
public class HomeController {
    PageRepository pageRepository;
    private GitHubAdapter gitHubAdapter;

    @Autowired
    HomeController(GitHubAdapter gitHubAdapter, PageRepository pageRepository) {
        this.gitHubAdapter = gitHubAdapter;
        this.pageRepository = pageRepository;
    }

    @RequestMapping(value = "/", method = RequestMethod.GET)
    public String home(Map<String, Object> model) throws ExecutionException {
        model.put("githubRepoCount", gitHubAdapter.getGitHubRepositoriesCount());
        return "home";
    }
}

最后,失败的测试:

package xyz.jacobclark.controllers;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import xyz.jacobclark.adapters.GitHubAdapter;

import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

@RunWith(SpringRunner.class)
@WebMvcTest(HomeController.class)
public class HomeControllerTest {

    @Autowired
    private MockMvc mvc;

    @MockBean
    private GitHubAdapter gitHubAdapter;

    @Test
    public void getOneRandomly() throws Exception {
        when(gitHubAdapter.getGitHubRepositoriesCount()).thenReturn("42");

        mvc.perform(get("/"))
                .andExpect(status().isOk())
                .andExpect(view().name("home"))
                .andExpect(model().attribute("githubRepoCount", "42"));
    }
}

4 个答案:

答案 0 :(得分:1)

根据文档,注释 var checkOverlap = function (a, b) { if ( ((a.left < b.left && a.left + a.width > b.left) || (a.left > b.left && a.left + a.width < b.left + b.width) || (a.left > b.left && a.left + a.width > b.left + b.width)) && ((a.top < b.top && a.top + a.height > b.top) || (a.top > b.top && a.top + a.height > b.top) || (a.top > b.top && a.top < b.top + b.height)) && (a.left < b.left + b.width) && (a.top < b.top + b.height) ) { return true; } };会阻止与MVC测试无关的组件自动装配,这包括@WebMvcTest,这就是上述代码不起作用的原因。

http://docs.spring.io/spring-boot/docs/current/api/org/springframework/boot/test/autoconfigure/web/servlet/WebMvcTest.html

答案 1 :(得分:0)

错误说: No qualifying bean of type 'xyz.jacobclark.repositories.PageRepository'

您已模拟GitHubAdapter但未提供任何配置来实例化PageRepository

我建议3个选项:

  1. 模拟PageRepository
  2. 提供@ContextConfiguration来实例化bean
  3. 如果null为ok,则将存储库标记为不需要:@Autowired(required = false)

答案 2 :(得分:0)

您的存储库。

@Repository
public interface PageRepository extends CrudRepository<Page, String> {
    List<Page> findById(String id);

    List<Page> findByTitle(String titlex);
}

此@Repository(注释)将删除并移至您的Implements(实现)。 dao(界面) - &gt;该注释使用 DaoImpl 类(implements)。

示例entityManagerFactory

<jpa:repositories base-package="xyz.jacobclark.repositories"></jpa:repositories>

样本库

不要使用Annotation(@Repository)。你在设置配置中使用base-package。

public interface AccountRepository extends PagingAndSortingRepository<Account, Integer>{

    public Account findById(String id);

    public List<Account> findByCompany(Company company);
}

您的控制器

@Controller
public class HomeController {
    PageRepository pageRepository;
    private GitHubAdapter gitHubAdapter;

    @Autowired
    HomeController(GitHubAdapter gitHubAdapter, PageRepository pageRepository) {
        this.gitHubAdapter = gitHubAdapter;
        this.pageRepository = pageRepository;
    }

    @RequestMapping(value = "/", method = RequestMethod.GET)
    public String home(Map<String, Object> model) throws ExecutionException {
        model.put("githubRepoCount", gitHubAdapter.getGitHubRepositoriesCount());
        return "home";
    }
}

样本控制器

您在控制器中使用服务 dao 。控制器是门。所以实现只是bean。我认为你需要搜索DI和IOC。我建议不要使用你的模拟测试。你使用junit测试。你打电话给单位(服务和道)。此链接:this Spring Tutorialthis jUnit + Spring Tutorial XD。

@RequestMapping(value = "/association")
@Controller
public class AssociationController {

    @Autowired
    private AssociationService associationService;

    @Autowired
    private AccountService accountService;

    @Autowired
    private CompanyService companyService;

    @Autowired
    private ProductService productService;

    @RequestMapping(value = "/settings/company")
    public ModelAndView showCompany(ModelAndView mav, HttpServletRequest req, Authentication auth) {
        CustomUserDetail customUserDetail = (CustomUserDetail) auth.getPrincipal();

        int associationIdx = customUserDetail.getAccount().getAssociation().getIdx();

        Association association = associationService.findAssociationByIdx(associationIdx);

        mav.addObject("association", association);
        mav.setViewName("/association/association_settings_company");

        return mav;
    }

    ....

}

答案 3 :(得分:0)

对我来说这是有效的:

  1. 对于任何需要模拟的 Spring-Data 存储库接口,请使用 @MockBean
  2. 对于任何 JPA 存储库实现类,都可以使用 @Mock(或其他标准方法)以标准方式模拟它们
  3. 单元测试 JPA+Spring-Data 可以在不使用内存数据库的情况下完成,所以不要假设您需要一个。
  4. 如果您的服务混合使用 Spring-Data 存储库接口和 JPA 实现的存储库类,则上述建议将有效。
  5. 我使用 @WebMvcTest 代替 @DataJpaTest 注释。并非每个人都需要使用 @WebMvcTest。