使用Mockito mocks注入Spring Boot组件



这是我的GitHub repo,用于复制确切的问题。

不确定这是Spring Boot问题还是Mockito问题。

我有以下Spring Boot@Component类:

@Component
class StartupListener implements ApplicationListener<ContextRefreshedEvent>, KernelConstants {
@Autowired
private Fizz fizz;
@Autowired
private Buzz buzz;
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
// Do stuff involving 'fizz' and 'buzz'
}
}

因此,StartupListener没有构造函数,而是有意通过@Autowired注入其属性的Spring@Component

提供这些依赖关系的@Configuration类就在这里:

@Configuration
public class MyAppConfiguration {
@Bean
public Fizz fizz() {
return new Fizz("OF COURSE");
}
@Bean
public Buzz buzz() {
return new Buzz(1, true, Foo.Bar);
}
}

我现在正试图为StartupListener编写一个JUnit单元测试,并且我已经成功地使用了Mockito。我想创建一个mockFizzBuzz实例,并用它们注入StartupListener,但我不确定如何:

public class StartupListenerTest {
private StartupListener startupListener;
@Mock
private Fizz fizz;
@Mock
price Buzz buzz;
@Test
public void on_startup_should_do_something() {
Mockito.when(fizz.calculateSomething()).thenReturn(43);
// Doesn't matter what I'm testing here, the point is I'd like 'fizz' and 'buzz' to be mockable mocks
// WITHOUT having to add setter methods to StartupListener and calling them from inside test code!
}
}

关于我如何才能做到这一点,有什么想法吗


更新

请参阅我的GitHub回购以复制这个确切的问题。

您可以使用@SpyBean而不是@MockBeanSpyBean封装真实bean,但允许您验证方法调用和模拟单个方法,而不会影响真实bean的任何其他方法。

@SpyBean
private Fizz fizz;
@SpyBean
price Buzz buzz;

您可以使用@MockBeanApplicationContext中模拟bean

我们可以使用@MockBean将mock对象添加到Spring应用程序上下文中。mock将替换应用程序上下文中任何现有的相同类型的bean。

如果没有定义相同类型的bean,那么将添加一个新的bean。这个注释在集成测试中很有用,在集成测试需要模拟特定的bean(例如,外部服务)。

要使用此注释,我们必须使用SpringRunner来运行测试:

@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest
public class MockBeanAnnotationIntegrationTest {

@MockBean
private Fizz fizz;
}

我还建议使用@SpringBootTest

@SpringBootTest注释告诉SpringBoot去寻找一个主配置类(例如一个带有@SpringBootApplication的类),并使用它来启动Spring应用程序上下文。

你也可以做一些类似的事情,

@RunWith(MockitoJUnitRunner.class)
public class StartupListenerTest {
@Mock
private Fizz fizz;
@Mock
price Buzz buzz;
@InjectMocks
private StartupListener startupListener;
@Test
public void on_startup_should_do_something() {
Mockito.when(fizz.calculateSomething()).thenReturn(43);
....
}
}

这里有一个简单的例子,它只使用普通的Spring。

package com.stackoverflow.q54318731;
import static org.junit.Assert.*;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationListener;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.stereotype.Component;
import org.springframework.test.context.junit4.rules.SpringClassRule;
import org.springframework.test.context.junit4.rules.SpringMethodRule;
@SuppressWarnings("javadoc")
public class Answer {
/** The Constant SPRING_CLASS_RULE. */
@ClassRule
public static final SpringClassRule SPRING_CLASS_RULE = new SpringClassRule();
/** The spring method rule. */
@Rule
public final SpringMethodRule springMethodRule = new SpringMethodRule();
static final AtomicInteger FIZZ_RESULT_HOLDER = new AtomicInteger(0);
static final int FIZZ_RESULT = 43;
static final AtomicInteger BUZZ_RESULT_HOLDER = new AtomicInteger(0);;
static final int BUZZ_RESULT = 42;
@Autowired
ConfigurableApplicationContext configurableApplicationContext;
@Test
public void test() throws InterruptedException {
this.configurableApplicationContext
.publishEvent(new ContextRefreshedEvent(this.configurableApplicationContext));
// wait for it
TimeUnit.MILLISECONDS.sleep(1);
assertEquals(FIZZ_RESULT, FIZZ_RESULT_HOLDER.get());
assertEquals(BUZZ_RESULT, BUZZ_RESULT_HOLDER.get());
}
@Configuration
@ComponentScan //so we can pick up the StartupListener 
static class Config {
final Fizz fizz = Mockito.mock(Fizz.class);
final Buzz buzz = Mockito.mock(Buzz.class);
@Bean
Fizz fizz() {
Mockito.when(this.fizz.calculateSomething())
.thenReturn(FIZZ_RESULT);
return this.fizz;
}
@Bean
Buzz buzz() {
Mockito.when(this.buzz.calculateSomethingElse())
.thenReturn(BUZZ_RESULT);
return this.buzz;
}
}
@Component
static class StartupListener implements ApplicationListener<ContextRefreshedEvent> {
@Autowired
private Fizz fizz;
@Autowired
private Buzz buzz;
@Override
public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) {
FIZZ_RESULT_HOLDER.set(this.fizz.calculateSomething());
BUZZ_RESULT_HOLDER.set(this.buzz.calculateSomethingElse());
}
}
static class Fizz {
int calculateSomething() {
return 0;
}
}
static class Buzz {
int calculateSomethingElse() {
return 0;
}
}
}

如果您修改StartupListenerTest以只关注StartupListener类

即,将类添加到SpringBootTest注释

@SpringBootTest(classes= {StartupListener.class})

你会得到一个不同的错误,但它更关注于你试图测试的类。

onApplicationEvent方法将在测试运行之前激发。这意味着您不会用when(troubleshootingConfig.getMachine()).thenReturn(machine);初始化mock,因此在调用getMachine()时不会返回Machine,因此会产生NPE。

解决这个问题的最佳方法实际上取决于你试图从测试中获得什么。我会使用application-test.properties文件来设置TroubleShootingConfig,而不是使用@MockBean。如果你在onApplicationEvent中所做的只是日志记录,那么你可以按照这个问题的另一个答案中的建议使用@SpyBean。以下是你的操作方法。

application-test.properties添加到resources文件夹中,使其位于类路径中:

troubleshooting.maxChildRestarts=4
troubleshooting.machine.id=machine-id
troubleshooting.machine.key=machine-key

@Configuration添加到TroubleshootingConfig

@Configuration
@ConfigurationProperties(prefix = "troubleshooting")
public class TroubleshootingConfig {
private Machine machine;
private Integer maxChildRestarts;
... rest of the class

更改StartupListenerTest以关注您测试的类,并监视TroubleshootingConfig。您还需要@EnableConfigurationProperties

@RunWith(SpringRunner.class)
@SpringBootTest(classes= {TroubleshootingConfig.class, StartupListener.class})
@EnableConfigurationProperties
public class StartupListenerTest   {
@Autowired
private StartupListener startupListener;
@SpyBean
private TroubleshootingConfig troubleshootingConfig;
@MockBean
private Fizzbuzz fizzbuzz;
@Mock
private TroubleshootingConfig.Machine machine;
@Mock
private ContextRefreshedEvent event;
@Test
public void should_do_something() {
when(troubleshootingConfig.getMachine()).thenReturn(machine);
when(fizzbuzz.getFoobarId()).thenReturn(2L);
when(machine.getKey()).thenReturn("FLIM FLAM!");
// when
startupListener.onApplicationEvent(event);
// then
verify(machine).getKey();
}
}

相关内容

  • 没有找到相关文章

最新更新