微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

将@Configuration 类定义的非模拟注入单元测试

如何解决将@Configuration 类定义的非模拟注入单元测试

Java 8、JUnit 4 和 Spring Boot 2.3 在这里我有一种情况,我有一个带有 @Component 注释的 Spring Boot 类,它获取 @Autowired 及其所有依赖项(bean 在 @Configuration 注释的配置类中定义):

@Configuration
public class SomeConfig {

    @Bean
    public List<Fizz> fizzes() {
        Fizz fizz = new Fizz(/*complex logic here*/);
        return new Collections.singletonList(fizz);
    }

    @Bean
    public Buzz buzz() {
        return new Buzz(/*complex logic here*/);
    }
    
}

@Component
public class ThingDoerinator {

    @Autowired
    private Lizz<Fizz> fizzes;

    @Autowired
    private Buzz buzz;

    public String doStuff() {
        // uses fizz and buzz extensively inside here...
    }
    
}

我可以轻松编写一个单元测试来将所有这些依赖项作为模拟注入:

public class ThingDoerinatorTest extends AbstractBaseTest {

    @Mock
    private List<Fizz> fizzes;

    @Mock
    private Buzz buzz;

    @InjectMocks
    private ThingDoerinator thingDoerinator;

    @Test
    public void should_do_all_the_stuff() {

        // given
        // Todo: specify fizzes/buzz mock behavior here

        // when
        String theThing = thingDoerinator.doStuff();

        // then
        // Todo: make some assertions,verify mock behavior,etc.

    }
}

而且 80%​​ 的时间对我有用。但是,我现在正在尝试编写一些更像 integration 测试的单元测试,其中不是注入的模拟,我希望 bean 像正常一样实例化并连接到 ThingDoerinator 类就像他们在生产中一样:

public class ThingDoerinatorTest extends AbstractBaseTest {

    @Mock
    private List<Fizz> fizzes;

    @Mock
    private Buzz buzz;

    @InjectMocks
    private ThingDoerinator thingDoerinator;

    @Test
    public void should_do_all_the_stuff() {

        // given
        // how do I grab the same Fizz and Buzz beans
        // that are defined in SomeConfig?

        // when -- Now instead of mocks,fizzes and buzz are actually being called
        String theThing = thingDoerinator.doStuff();

        // then
        // Todo: make some assertions,etc.

    }
}

我怎样才能做到这一点?

解决方法

您可以使用 SpringBootTest。

@SpringBootTest(classes = {Fizz.class,Buzz.class,ThingDoerinator.class})
@RunWith(SpringRunner.class)
public class ThingDoerinatorTest {
    @Autowired
    private Fizz fizz;

    @Autowired
    private Buzz buzz;

    @Autowired
    private ThingDoerinator thingDoerinator;
}
,

您不需要模拟任何东西,只需在您的测试中注入您的类,您的配置就会向您的 ThingDoerinator 类提供 bean,您的测试用例将像调用 {{1} } 来自某个控制器或其他服务的方法。

TL;DR

我认为,您对运行整个 spring 上下文的测试和单元测试感到困惑,它们只测试我们编写的函数/逻辑而不运行整个上下文。编写单元测试是为了测试函数片段,如果该函数在给定的输入集下按预期运行。

理想的单元测试是不需要模拟的,我们只传递输入并产生输出(纯函数)但在实际应用中通常情况并非如此,我们可能会在交互时发现自己处于这种情况与我们应用程序中的其他一些对象。由于我们不打算测试该对象交互,而是关注我们的功能,因此我们模拟了该对象行为。

看来,您对单元测试没有问题,因此您可以在测试类 ThingDoerinator. doStuff() 中模拟 bean 的 List 并将它们注入您的测试用例中,并且您的测试用例运行良好。

现在,你想用 ThingDoerinator 做同样的事情,所以我举一个假设的例子来证明你现在不需要模拟对象,因为 spring 上下文会有它们,当 {{1 }} 将加载整个上下文来运行您的单个测试用例。

假设我有一个服务 @SpringBootTest 并且它有一个方法 @springBootTest

FizzService

此处使用构造函数注入1

现在,我的 getFizzes() 将通过配置(类似于您的情况)创建,并被告知 spring 上下文根据需要注入它们。

@Service
public class FizzService {
    private final List<Fizz> fizzes;

    public FizzService(List<Fizz> fizzes) {
        this.fizzes = fizzes;
    }

    public List<Fizz> getFizzes() {
        return Collections.unmodifiableList(fizzes);
    }
}

暂时忽略@Profile 注解2

现在我将进行 spring boot 测试,以检查此服务是否会提供与我提供给上下文的 List<Fizzes 相同的列表,以及它何时将在生产环境中运行

@Profile("dev")
@Configuration
public class FizzConfig {

    @Bean
    public List<Fizz> allFizzes() {
        return asList(Fizz.of("Batman"),Fizz.of("Aquaman"),Fizz.of("Wonder Woman"));
    }
}

现在,当我运行测试时,我看到它有效,那么它是如何工作的,让我们了解一下。因此,当我使用 fizzes 注释运行测试时,它的运行方式与在生产环境中使用嵌入式服务器运行时类似。

所以,我之前创建的配置类将被扫描,然后在那里创建的 bean 将被加载到上下文中,我的服务类也用 @ActiveProfiles("test") @SpringBootTest class FizzServiceTest { @Autowired private FizzService service; @Test void shouldGiveAllTheFizzesInContext() { List<Fizz> fizzes = service.getFizzes(); assertThat(fizzes).isNotNull().isNotEmpty().hasSize(3); assertThat(fizzes).extracting("name") .contains("Wonder Woman","Aquaman"); } } 注释进行了注释,这样它也将被加载和 spring将识别它需要 @SpringBootTest 然后它会查看它的上下文,如果它有那个 bean,那么它就会找到并注入它,因为我们是从我们的配置类提供它。

在我的测试中,我自动连接服务类,所以 spring 将注入它拥有的 @Service 对象,它也会有我的 List<Fizz>(在前面的段落中解释)。

所以,你不必做任何事情,如果你定义了你的配置并且那些正在加载并在生产中工作,那些应该在你的 spring 引导测试中以相同的方式工作,除非你有一些自定义设置而不是默认 spring启动应用程序。

现在,让我们看一个案例,当您可能希望在测试中使用不同的 FizzService 时,在这种情况下,您将不得不排除生产配置并包含一些测试配置。

在我的示例中,我将在我的测试文件夹中创建另一个 List<Fizz> 并使用 List<Fizz> 注释它

FizzConfig

我会稍微改变我的测试

@TestConfiguration

使用不同的配置文件进行测试,因此使用@ActiveProfile 注释,否则默认配置文件集是 @TestConfiguration public class FizzConfig { @Bean public List<Fizz> allFizzes() { return asList(Fizz.of("Flash"),Fizz.of("Mera"),Fizz.of("Superman")); } } ,它将加载生产 @ActiveProfiles("test") @SpringBootTest @Import(FizzConfig.class) class FizzServiceTest { @Autowired private FizzService service; @Test void shouldGiveAllTheFizzesInContext() { List<Fizz> fizzes = service.getFizzes(); assertThat(fizzes).extracting("name") .contains("Flash","Superman"); } } 类,因为它会扫描所有包( 2记住上面的 dev 注释,这将确保之前的配置只在 production/dev env 中运行)。这是我的 application.yml 以使其正常工作。

FizzConfig

另外,请注意,我在此处导入带有 @Profile 注释的 spring: profiles: active: dev --- spring: profiles: test ,您也可以使用 FizzConfiguration 执行相同的操作。

因此,您可以看到测试用例的值与生产代码中的值不同。

编辑

如注释所述,由于您不希望在此测试中测试连接到数据库,因此您必须禁用 spring 数据 JPA 的自动配置,默认情况下,spring boot 会这样做。

3您可以创建另一个配置类,如

@Import

然后将其包含在您的 @SpringBootTest(classes = FizzConfig.class) 中,这将禁用默认数据库设置,然后您将面临最后一个难题,即您可能在课堂上进行的任何存储库交互。

因为现在,您的测试用例中没有自动配置,这些存储库只是接口,您可以在测试类中轻松模拟

@Configuration
@EnableAutoConfiguration(exclude = {DataSourceAutoConfiguration.class,DataSourceTransactionManagerAutoConfiguration.class,HibernateJpaAutoConfiguration.class})
public TestDataSourceConfiguration {}

1在生产代码中建议使用构造函数注入而不是`@Autowired`,如果你的依赖项确实需要该类工作,所以请尽可能避免它。
3请注意,您只能在测试包中创建此类配置或使用一些配置文件进行标记,请勿在您的生产包中创建它,否则它将禁用您运行代码的数据库。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。