【问题标题】:How to make instance of CrudRepository interface during testing in Spring?如何在 Spring 测试期间创建 CrudRepository 接口的实例?
【发布时间】:2015-03-07 12:29:24
【问题描述】:

我有一个 Spring 应用程序,其中我使用 xml 配置,仅使用 Java Config。一切都很好,但是当我尝试 test 时,我遇到了在测试中启用组件自动装配的问题。那么让我们开始吧。我有一个界面

@Repository
public interface ArticleRepository extends CrudRepository<Page, Long> {
    Article findByLink(String name);
    void delete(Page page);
}

还有一个组件/服务:

@Service
public class ArticleServiceImpl implements ArticleService {
    @Autowired
    private ArticleRepository articleRepository;
...
}

我不想使用 xml 配置,因此对于我的测试,我尝试仅使用 Java 配置来测试 ArticleServiceImpl。所以为了测试目的我做了:

@Configuration
@ComponentScan(basePackages = {"com.example.core", "com.example.repository"})
public class PagesTestConfiguration {


@Bean
public ArticleRepository articleRepository() {
       // (1) What to return ?
}

@Bean
public ArticleServiceImpl articleServiceImpl() {
    ArticleServiceImpl articleServiceImpl = new ArticleServiceImpl();
    articleServiceImpl.setArticleRepository(articleRepository());
    return articleServiceImpl;
}

}

articleServiceImpl() 我需要放置 articleRepository() 的实例,但它是一个接口。如何使用新关键字创建新对象?是否可以不创建 xml 配置类并启用自动装配?测试时只使用 JavaConfigurations 可以启用自动装配吗?

【问题讨论】:

  • 不,你没有。你有@Autowired 所以你不需要设置它。您需要将 @EnableJpaRepositories 放在您的配置类中,让 Spring Data JPA 为您创建 bean。
  • 对于 ArticleServiceImpl 我也有 Awtowire 所以我也不需要写 articleServiceImpl() 吗?我对吗?我不明白 Spring 是如何知道为测试打开自动装配的。创建名为“articleServiceImpl”的 bean 时出错:注入自动装配的依赖项失败;嵌套异常是 org.springframework.beans.factory.BeanCreationException:无法自动装配字段:私有 com.musala.repository.ArticleRepository
  • @M.Deinum 有正确答案..
  • 对于单元测试,根本不要使用真正的存储库。重构您的服务以使用构造函数注入并注入模拟存储库。这将使您的测试更加独立且速度更快。

标签: java xml spring spring-mvc autowired


【解决方案1】:

这就是我发现的 spring 控制器测试的最小设置,它需要自动装配的 JPA 存储库配置(使用带有嵌入式 spring 4.1.4.RELEASE 的 spring-boot 1.2,DbUnit 2.4.8)。

测试针对嵌入式 HSQL 数据库运行,该数据库在测试开始时由 xml 数据文件自动填充。

测试类:

@RunWith( SpringJUnit4ClassRunner.class )
@ContextConfiguration( classes = { TestController.class,
                                   RepoFactory4Test.class } )
@TestExecutionListeners( { DependencyInjectionTestExecutionListener.class,
                           DirtiesContextTestExecutionListener.class,
                           TransactionDbUnitTestExecutionListener.class } )
@DatabaseSetup( "classpath:FillTestData.xml" )
@DatabaseTearDown( "classpath:DbClean.xml" )
public class ControllerWithRepositoryTest
{
    @Autowired
    private TestController myClassUnderTest;

    @Test
    public void test()
    {
        Iterable<EUser> list = myClassUnderTest.findAll();

        if ( list == null || !list.iterator().hasNext() )
        {
            Assert.fail( "No users found" );
        }
        else
        {
            for ( EUser eUser : list )
            {
                System.out.println( "Found user: " + eUser );
            }
        }
    }

    @Component
    static class TestController
    {
        @Autowired
        private UserRepository myUserRepo;

        /**
         * @return
         */
        public Iterable<EUser> findAll()
        {
            return myUserRepo.findAll();
        }
    }
}

注意事项:

  • @ContextConfiguration 注解,仅包含嵌入式TestController 和JPA 配置类RepoFactory4Test。

  • 需要@TestExecutionListeners注解才能使后续注解@DatabaseSetup和@DatabaseTearDown生效

引用的配置类:

@Configuration
@EnableJpaRepositories( basePackageClasses = UserRepository.class )
public class RepoFactory4Test
{
    @Bean
    public DataSource dataSource()
    {
        EmbeddedDatabaseBuilder builder = new EmbeddedDatabaseBuilder();
        return builder.setType( EmbeddedDatabaseType.HSQL ).build();
    }

    @Bean
    public EntityManagerFactory entityManagerFactory()
    {
        HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
        vendorAdapter.setGenerateDdl( true );

        LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
        factory.setJpaVendorAdapter( vendorAdapter );
        factory.setPackagesToScan( EUser.class.getPackage().getName() );
        factory.setDataSource( dataSource() );
        factory.afterPropertiesSet();

        return factory.getObject();
    }

    @Bean
    public PlatformTransactionManager transactionManager()
    {
        JpaTransactionManager txManager = new JpaTransactionManager();
        txManager.setEntityManagerFactory( entityManagerFactory() );
        return txManager;
    }
}

UserRepository 是一个简单的接口:

public interface UserRepository extends CrudRepository<EUser, Long>
{
}   

EUser 是一个简单的@Entity 注释类:

@Entity
@Table(name = "user")
public class EUser
{
    @Id
    @Column(name = "id")
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Max( value=Integer.MAX_VALUE )
    private Long myId;

    @Column(name = "email")
    @Size(max=64)
    @NotNull
    private String myEmail;

    ...
}

FillTestData.xml:

<?xml version="1.0" encoding="UTF-8"?>
<dataset>
    <user id="1"
          email="alice@test.org"
          ...
    />
</dataset>

DbClean.xml:

<?xml version="1.0" encoding="UTF-8"?>
<dataset>
    <user />
</dataset>

【讨论】:

  • 感谢这篇文章。你能帮我理解RepoFactory4Test类的真正好处吗?
  • 它是单元测试类中@ContextConfiguration注解中声明的两个bean之一。这意味着 spring 只为测试运行自动加载这两个 bean。 RepoFactory4Test 本身提供了一些 JPA 工作所需的更多 bean。除此之外,它还定义了要使用的存储库(此处为@EnableJpaRepositories
  • 感谢您提供详细信息。但是,根据我的要求,我应该加载 .sql 文件而不是 .xml 文件(如上面的示例中所述)并寻找一种可以在测试类本身中加载它们的方法,在这种情况下为 @987654333 @ 班级。到目前为止,我已经修改了上面示例中的dataSource() 方法,添加了加载sqls 的addScript()。但是,寻找一个通用的解决方案。请提出建议。
【解决方案2】:

如果您使用 Spring Boot,您可以通过添加 @SpringBootTest 来加载您的 ApplicationContext 来稍微简化这些方法。这允许您在您的 spring-data 存储库中自动装配。请务必添加@RunWith(SpringRunner.class),以便获取特定于弹簧的注释:

@RunWith(SpringRunner.class)
@SpringBootTest
public class OrphanManagementTest {

  @Autowired
  private UserRepository userRepository;

  @Test
  public void saveTest() {
    User user = new User("Tom");
    userRepository.save(user);
    Assert.assertNotNull(userRepository.findOne("Tom"));
  }
}

您可以在他们的docs 中阅读有关在 Spring Boot 中进行测试的更多信息。

【讨论】:

  • 我用@WebMvcTest 运行它,不得不用@SpringBootTest@AutoConfigureMockMvc 替换它
  • @Pedro 当我发布这个答案时,我在 Spring Boot 1 上
  • 它看起来像集成测试,在生产中你会遇到问题
【解决方案3】:

您不能在配置类中使用存储库,因为从配置类中它会使用 @EnableJpaRepositories 找到所有存储库。

  1. 因此,将您的 Java 配置更改为:
@Configuration
@EnableWebMvc
@EnableTransactionManagement
@ComponentScan("com.example")
@EnableJpaRepositories(basePackages={"com.example.jpa.repositories"})//Path of your CRUD repositories package
@PropertySource("classpath:application.properties")
public class JPAConfiguration {
  //Includes jpaProperties(), jpaVendorAdapter(), transactionManager(), entityManagerFactory(), localContainerEntityManagerFactoryBean()
  //and dataSource()  
}
  1. 如果您有许多存储库实现类,则创建一个单独的类,如下所示
@Service
public class RepositoryImpl {
   @Autowired
   private UserRepositoryImpl userService;
}
  1. 在您的控制器中自动连接到 RepositoryImpl,然后您可以从那里访问您的所有存储库实现类。
@Autowired
RepositoryImpl repository;

用法:

repository.getUserService().findUserByUserName(userName);

移除 ArticleRepository 中的@Repository 注解,ArticleServiceImpl 应该实现 ArticleRepository 而不是 ArticleService。

【讨论】:

    【解决方案4】:

    你需要做的是:

    1. ArticleRepository中删除@Repository

    2. @EnableJpaRepositories 添加到PagesTestConfiguration.java

      @Configuration
      @ComponentScan(basePackages = {"com.example.core"}) // are you sure you wanna scan all the packages?
      @EnableJpaRepositories(basePackageClasses = ArticleRepository.class) // assuming you have all the spring data repo in the same package.
      public class PagesTestConfiguration {
      
      @Bean
      public ArticleServiceImpl articleServiceImpl() {
          ArticleServiceImpl articleServiceImpl = new ArticleServiceImpl();
          return articleServiceImpl;
      }
      }
      

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-10-30
      • 2018-07-12
      • 2011-03-29
      • 1970-01-01
      • 2016-07-21
      相关资源
      最近更新 更多