我的应用程序代码中有以下组件。在应用程序启动时,我想调用一些 init 方法。但是在测试期间,当调用这些方法时,我不想执行任何操作。

    import lombok.extern.slf4j.Slf4j;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.context.event.ApplicationReadyEvent;
    import org.springframework.context.event.EventListener;
    import org.springframework.stereotype.Service;
    
    @Slf4j
    @Service
    public class ManagementService {
        @Autowired
        private Temp temp;
    
        @EventListener(ApplicationReadyEvent.class)
        public void startup() {
            log.info("Startup on application ready.");
            temp.print();
        }
    }
    
    @Slf4j
    @Component
    public class Temp {
        public void print() {
            log.info("Inside temp");
        }
    }

我使用 @BeforeEach 来存根方法,如下所示:

    @SpringBootTest
    public abstract class SpringIntegrationBaseTest {
        @SpyBean
        protected ManagementService service;
        @SpyBean
        protected Temp temp;
       
    
        @BeforeEach
        public void preventConnection() throws SQLException {
            Mockito.doNothing().when(temp).print();
        }
    }

但是,我注意到第一个单元测试的日志显示:

    2024-10-23 10:24:16.675  INFO 23628 [           main] o.a.c.l.i.Jdk14Logger                    : Started ManagementServiceTests in 3.382 seconds (process running for 4.907)
    2024-10-23 10:24:16.694  INFO 23628 [           main] c.c.s.ManagementService                  : Startup on application ready.
    2024-10-23 10:24:16.696  INFO 23628 [           main] c.c.s.Temp                               : Inside temp

在第二个单元测试中,它没有正确地对 print() 执行任何操作。

    2024-10-23 10:36:36.799  INFO 17496 [           main] o.a.c.l.i.Jdk14Logger                    : Started RestControllerTest in 0.821 seconds (process running for 5.924)
    2024-10-23 10:36:36.801  INFO 17496 [           main] c.c.s.ManagementService                  : Startup on application ready.

进一步检查后,我注意到存根仅在测试之前直接完成,并且当应用程序上下文初始化时,由于存根尚未完成,因此仍会调用 print() 方法,但在下一个测试中,先前的存根将被延续。

我认为它看起来应该是这样的:

应用程序上下文初始化 –> 事件监听器调用 –> BeforeEach 存根 –> 测试

我想要这样的东西:

??? –> 存根 –> ??? –> 事件监听器调用 –> ??? –> 测试

其中 ??? 是任何中间步骤

我发现我可以使用@MockBean 来模拟 Temp 并且默认情况下不对所有方法执行任何操作,但是在测试期间我需要使用某些方法的实际行为,这就是为什么我想使用@SpyBean 而不必对测试中使用的所有方法进行存根。

编辑:我也尝试在 SpringIntegrationBaseTest 中使用 @PostConstruct,如下所示:

@PostConstruct
public void init() throws Exception {
    log.info("postconstruct init");
    Mockito.doNothing().when(temp).print();
}

但是,这也会在事件监听器注释方法之后执行。


最佳答案
3

在这种情况下,您可以使用配置文件。

@Component
@Profile("!test")
public class ManagementService

注意 test 前面的“!”,以便用 @Profile 注释您的类。这意味着只有当配置文件不是“test”时才应加载此类。

使用以下注释来注释你的测试类@ActiveProfiles("test")

@SpringBootTest
@ActiveProfiles("test")
public abstract class SpringIntegrationBaseTest

1

  • 我认为,虽然您的回答与 @AyoK 关于配置文件的回答类似,但如果测试期间未加载该类,您的回答实际上可能会破坏我的测试用例,并且我可能无法自动连接并获取 NPE。我将在周一确认这一点并回复您


    – 


您可以使用配置文件来控制行为,方法是创建一个方法来检查它是否在测试环境中运行,然后在测试属性文件中将活动配置文件定义为,test或者您可以使用注释测试类@ActiveProfiles("test")

    import org.springframework.core.env.Environment;
    .
    .
    .
    @Autowired
    private Environment environment

    @EventListener(ApplicationReadyEvent.class)
    public void startup() {
        if (!isTestEnvironment()) {
            log.info("Startup on application ready.");
            temp.print();
        }
    }

    private boolean isTestEnvironment() {
        return Arrays.asList(environment.getActiveProfiles()).contains("test");
    }

2

  • 1
    哦,等等,我刚刚看到了 @ActiveProfiles 上的评论。我的眼睛一下子就跳到了代码上。我必须在周一测试一下,然后才能确认任何事情


    – 

  • 你好,Ayo K。我刚刚测试了你的代码,虽然从技术上讲它是可行的,但我发现它破坏了我的部分测试用例(我正在验证该方法被调用的次数),虽然我可以将这个活动配置文件检查移到方法内部,但print()我会再等一段时间,看看是否有其他解决方案可以在不更改源代码的情况下满足我最初的需求


    – 

我设法想出了一个可行的答案(在我看来)。起初我的想法是延迟 EventListener,方法是让 ManagementService 监听其他自定义事件,StubInjectionComplete.class即让一个类监听 ApplicationReadyEvent,该类将在发布上述自定义事件之前拦截并注入存根。

但是,我决定只使用一个简单的 java 类来保存我可以稍后运行的存根,并使用@TestConfiguration,我将存根作为可调用函数注入到注入器中(可运行函数不能抛出已检查的异常):

public class StubInjector {
    private final Callable injected;

    public StubInjector(Callable injected) {
        this.injected = injected;
    }

    public void run() throws Exception {
        this.injected.call();
    }
}

@TestConfiguration
@Slf4j
public class StubConfiguration {
    @Bean
    public StubInjector getInjector(Temp temp) {
        return new StubInjector(() -> {
            log.info("Injected stubs");
            Mockito.doNothing().when(temp).print();
            return null;
        });
    }
}

我修改了现有的代码和单元测试,如下所示:

@SpringBootTest
@ActiveProfiles("test")  //<--
@Import(StubConfiguration.class)  //<--
public abstract class SpringIntegrationBaseTest {
    ...
}

public class ManagementService {
    @Autowired
    private StubInjector injector;
    ...

    @EventListener(ApplicationReadyEvent.class)
    public void startup() throws Exception {
        log.info("Startup on application ready.");
        injector.run();  //<--
        ...
    }
    ...
}

@Configuration
public class MiscConfig {
    @Bean
    @Profile("!test")
    public StubInjector stubInjector() {
        return new StubInjector(()-> null);
    }
}

现在,在常规运行中,除了对空的可调用函数进行额外调用之外,它什么也不做,但在单元测试中,我会在 ApplicationReadyEvent 上执行常规代码之前注入一些存根。