使用Spring Data和MongoDB进行单元/集成测试时无法模拟Repository

3

一些小信息事先说明:

  • 这与端对端测试无关,它指的是目前涉及多个模块的集成测试。
  • 由于之前遇到了几个困难,我们不得不改变很多测试代码,我正在努力降低集成级别,使我们回归单元测试。然而,在减少配置和其他内容之前,我必须先让所有内容重新起作用。
  • 如果您已经阅读了最后一段,您已经知道,但无论如何:我知道这不是一个好方法,我正在努力改变它,但我必须首先这样做。

依赖关系

Spring Boot 1.3.0
Spring Mongo 1.3.3
Spring Security 3.1.4
Spring Security Cas 4.0.2
Flapdoodle Embedmongo 1.46.0

基础

现在,我们有一个带有注释的抽象测试类

@RunWith(SpringJUnit4ClassRunner.class), @SpringApplicationConfiguration(TestConfig.class)@WebAppConfiguration

TestConfig-class看起来像这样:

@Configuration
@EnableAutoConfiguration(exclude = { EmbeddedMongoAutoConfiguration.class })
@ComponentScan(value = { "package1", "package2" })
public class TestConfig {

}

正如你所看到的,它扫描了这两个包中的所有内容,并提取了几个其他的配置类:

@Configuration
// needed if working with Spring-Data-Repository interfaces and subprojects
// http://stackoverflow.com/questions/29084824/spring-repository-components-not-found-in-gradle-subproject-springboot
@EnableMongoRepositories({ "package2.subpackage" })
public class ModelConfiguration {

}

并且

@Configuration
@EnableAutoConfiguration(exclude = { EmbeddedMongoAutoConfiguration.class })
@EnableMongoRepositories(basePackages = { "package2" })
public class TestMongoConfiguration {

    private static final String DESTROY_METHOD_CLOSE = "close";
    private static final String DESTROY_METHOD_STOP = "stop";

    private static final MongodStarter STARTER = MongodStarter.getDefaultInstance();

    @Autowired
    private MongoProperties mongoProperties;

    @Autowired(required = false)
    private MongoClientOptions mongoClientOptions;

    @Autowired
    private Environment environment;

    @Bean(destroyMethod = DESTROY_METHOD_CLOSE)
    public MongoClient mongo() throws IOException {
        Net net = mongodProcess().getConfig().net();
        mongoProperties.setHost(net.getServerAddress().getHostName());
        mongoProperties.setPort(net.getPort());
        return mongoProperties.createMongoClient(this.mongoClientOptions, environment);
    }

    @Bean(destroyMethod = DESTROY_METHOD_STOP)
    public MongodProcess mongodProcess() throws IOException {
        return mongodExecutable().start();
    }

    @Bean(destroyMethod = DESTROY_METHOD_STOP)
    public MongodExecutable mongodExecutable() throws IOException {
        return STARTER.prepare(mongodConfig());
    }

    @Bean
    public IMongodConfig mongodConfig() throws IOException {
        return new MongodConfigBuilder().version(Version.Main.PRODUCTION).build();
    }
}

这是我的测试类的一部分:

@SpringApplicationConfiguration(classes = TestClass.TestConfiguration.class)
public class TestClass extends AbstractTest {

    @Order(Ordered.HIGHEST_PRECEDENCE)
    public static class TestConfiguration {
        @Bean
        @Primary
        public FooRepository fooRepository() {
            FooRepository mock = mock(FooRepository.class);
            // mockbehaviour
            return mock;
        }

        @Bean
        @Primary
        public FooDao fooDao() {
            FooDao mock = mock(FooDao.class);
            //Mock behaviour
            return mock;
        }

        @Bean
        @Primary
        public BarDao barDao() {
            BarDao mock = mock(BarDao.class);
            //MockBehaviour
            return mock;
        }

        @Bean
        @Primary
        public BarRepository barRepository() {
            BarRepository mock = mock(BarRepository.class);
            return mock;
        }
    }

我现在正在创建多个请求,这些请求将被传递给超类进行安全测试,然后(由于所有测试都只有一个成功的情况),我尝试通过verify(mock)来验证对应的Daos和Repositories的调用。
这些Bean是通过@Resource注入到控制器中的,我在测试类中也是这样使用它们的。
问题:
由于某种原因,Daos按预期工作(被创建、行为被指定、被调用并可以验证),而Repositories则没有——常规代理被创建并使用。在使用@Order(Ordered.HIGHEST_PRECEDENCE)之前,bean的创建被简单地跳过了(并记录为这样),因为顶层bean已经注册并拒绝被覆盖。
在重新排序后,Repository-beans已经被覆盖而不是被跳过(当然,也被记录下来)。bean级别的@Primary应该确保使用模拟对象而不是组件扫描创建的实际bean,在其他不启动整个应用程序和嵌入式Mongo的测试实例上,这是按预期工作的,但在这里,它却没有起作用。
我尝试过的事情:
- 设置@Order - 排除@EnableMongoRepositories配置 - 手动注入创建的Mocks - 彻底的网络研究 - 设置bean角色 - 重新触发bean的创建 - 在创建dao-bean时触发bean的创建
我已经寻找了几天,唯一可能的解决方案是:创建一个xml配置(这是不可行的,因为我们应该创建一切而不需要xml文件),使用Fongo(这将需要导入一个新的依赖项来完成这一步,因为数据库测试完全发生在其他地方,然后删除它),或禁用bean覆盖(我完全不知道如何做,Spring文档也没有帮助——是的,方法在那里,但每当我尝试这样做时,它都不影响当前上下文)。

你能提供org.springframework的日志设置为调试级别吗? - František Hartman
@frant.hartm 是的,但我宁愿不张贴整个日志——如果您能给我一个大致的提示,我会很乐意张贴。 然而,我看到在我的bean正常加载后,Spring Data mongoBD正在注册自己的存储库,然后扫描FooRepositoryImpl.class(该类不存在),之后,它只是覆盖已经注册的FooRepository。 - Stefan Helmerichs
1个回答

3
抱歉,这更适合作为评论而不是答案,但格式太糟糕了... 我不知道为什么它不起作用,但你是否尝试使用显式导入@Configuration bean而不是使用ComponenScan?例如:
@SpringApplicationConfiguration(classes = TestClass.TestConfiguration.class)
public class TestClass extends AbstractTest {

    @Configuration
    @Import( TestConfig.class )
    public static class TestConfiguration {
       //...
    }
}

@Configuration
@EnableAutoConfiguration(exclude = { EmbeddedMongoAutoConfiguration.class })
// @ComponentScan(value = { "package1", "package2" }) // GET RID OF THIS
@Import( { TestMongoConfiguration.class /*, OtherConfig.class, ... */ } )
public class TestConfig {

}

通过这种方式,您可以控制配置加载的顺序。


我没有尝试过这个,因为我们几乎所有的组件(repositories、daos、controller 等)都是由 componentScan 加载的。而且,我认为顺序在这里并不重要——如果我的配置在 mongo 之后加载,我的 repositorybean 就会被简单地跳过;如果它在 mongo 之前加载,它就会被覆盖。 完全摆脱 ComponentScan 确保我的模拟被创建,但然后嵌入式 mongo 要么不会启动,要么我们的安全上下文不会正确加载(因此禁止每个请求)。 - Stefan Helmerichs
1
有趣的是,在漫长的操作之后,我们发现了罪魁祸首。有人已经尝试创建测试,他的类被注释为 @Configuration ,但不在相应的包中。此类具有 @EnableAutoConfiguration ,但没有显式排除 EmbeddedMongoAutoConfiguration ,这就覆盖了我们的 repositorymocks。在消除这个问题,并通过向每个类添加单独的测试配置来确保配置是专门针对测试的之后,问题解决了。所以感谢您的建议 =) - Stefan Helmerichs

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接