使用 post 构造测试 spring bean
Testing spring bean with post construct
我有一个类似这样的豆子:
@Service
public class A {
@Autowired
private B b;
@PostConstruct
public void setup() {
b.call(param);
}
}
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = { Application.class, Config.class })
@WebIntegrationTest(randomPort = true)
public class Test {
@Autowired
B b;
@Before
public void setUp() throws Exception {
when(b.call(any())).thenReturn("smth");
}
@Test
public void test() throws Exception {
// test...
}
}
问题是当测试为 运行 时,PostConstruct
在 setUp
之前被调用。
如果要编写 A
的 unit 测试,则不要使用 Spring。相反,自己实例化 A
并传递 B
的 stub/mock(通过使用构造函数注入或 ReflectionTestUtils
设置私有字段)。
例如:
@Service
public class A {
private final B b;
@Autowired
public A(B b) {
this.b = b;
}
@PostConstruct
public void setup() {
b.call(param);
}
}
-
public class Test {
@Test
public void test() throws Exception {
B b = mock(b);
A a = new A(b);
// write some tests for A
}
}
如果您必须使用 Spring,因为您要编写 集成 测试,请使用不同的应用程序上下文,将 B
替换为stub/mock.
例如,假设 B
在 Production
class 中实例化如下:
@Configuration
public class Production {
@Bean
public B b() {
return new B();
}
}
为您的测试写另一个 @Configuration
class:
@Configuration
public class Tests {
@Bean
public B b() {
// using Mockito is just an example
B b = Mockito.mock(B.class);
Mockito.when(b).thenReturn("smth");
return b;
}
}
在您的测试中使用 @SpringApplicationConfiguration
注释引用它:
@SpringApplicationConfiguration(classes = { Application.class, Tests.class })
另一种方法是自己在测试中实例化应用程序上下文,然后在刷新上下文之前注入模拟,例如:
@Configuration
@ComponentScan
public class TestConfiguration {}
...
ClassToMock mock = mock(ClassToMock.class);
AnnotationConfigApplicationContext c = new AnnotationConfigApplicationContext();
c.getDefaultListableBeanFactory().registerResolvableDependency(
ClassToMock.class,
mock);
c.register(TestConfiguration.class);
c.refresh();
当上下文中有 @PostConstruct
注释并且您想在模拟之前设置期望时,此替代方法很有用。
刚刚在我正在进行的项目中遇到了这个确切的问题,这是我在问题代码方面使用的解决方案:
@Autowire
中的bean用@PostConstruct
给你测试。
- 在
@Before
中进行设置。
- 明确调用
@Before
末尾的 @PostConstruct
。
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = { Application.class, Config.class })
@WebIntegrationTest(randomPort = true)
public class Test {
// wire in the dependency as well
@Autowired
A a;
@Autowired
B b;
@Before
public void setUp() throws Exception {
when(b.call(any())).thenReturn("smth");
// "manual" call to @PostConstruct which will now work as expected
a.setup();
}
@Test
public void test() throws Exception {
// test...
}
}
显然您的 @PostConstruct
方法必须是幂等的,因为它会被调用两次。它还假定默认的单例 bean 行为。
我有一个类似这样的豆子:
@Service
public class A {
@Autowired
private B b;
@PostConstruct
public void setup() {
b.call(param);
}
}
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = { Application.class, Config.class })
@WebIntegrationTest(randomPort = true)
public class Test {
@Autowired
B b;
@Before
public void setUp() throws Exception {
when(b.call(any())).thenReturn("smth");
}
@Test
public void test() throws Exception {
// test...
}
}
问题是当测试为 运行 时,PostConstruct
在 setUp
之前被调用。
如果要编写 A
的 unit 测试,则不要使用 Spring。相反,自己实例化 A
并传递 B
的 stub/mock(通过使用构造函数注入或 ReflectionTestUtils
设置私有字段)。
例如:
@Service
public class A {
private final B b;
@Autowired
public A(B b) {
this.b = b;
}
@PostConstruct
public void setup() {
b.call(param);
}
}
-
public class Test {
@Test
public void test() throws Exception {
B b = mock(b);
A a = new A(b);
// write some tests for A
}
}
如果您必须使用 Spring,因为您要编写 集成 测试,请使用不同的应用程序上下文,将 B
替换为stub/mock.
例如,假设 B
在 Production
class 中实例化如下:
@Configuration
public class Production {
@Bean
public B b() {
return new B();
}
}
为您的测试写另一个 @Configuration
class:
@Configuration
public class Tests {
@Bean
public B b() {
// using Mockito is just an example
B b = Mockito.mock(B.class);
Mockito.when(b).thenReturn("smth");
return b;
}
}
在您的测试中使用 @SpringApplicationConfiguration
注释引用它:
@SpringApplicationConfiguration(classes = { Application.class, Tests.class })
另一种方法是自己在测试中实例化应用程序上下文,然后在刷新上下文之前注入模拟,例如:
@Configuration
@ComponentScan
public class TestConfiguration {}
...
ClassToMock mock = mock(ClassToMock.class);
AnnotationConfigApplicationContext c = new AnnotationConfigApplicationContext();
c.getDefaultListableBeanFactory().registerResolvableDependency(
ClassToMock.class,
mock);
c.register(TestConfiguration.class);
c.refresh();
当上下文中有 @PostConstruct
注释并且您想在模拟之前设置期望时,此替代方法很有用。
刚刚在我正在进行的项目中遇到了这个确切的问题,这是我在问题代码方面使用的解决方案:
@Autowire
中的bean用@PostConstruct
给你测试。- 在
@Before
中进行设置。 - 明确调用
@Before
末尾的@PostConstruct
。
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = { Application.class, Config.class })
@WebIntegrationTest(randomPort = true)
public class Test {
// wire in the dependency as well
@Autowired
A a;
@Autowired
B b;
@Before
public void setUp() throws Exception {
when(b.call(any())).thenReturn("smth");
// "manual" call to @PostConstruct which will now work as expected
a.setup();
}
@Test
public void test() throws Exception {
// test...
}
}
显然您的 @PostConstruct
方法必须是幂等的,因为它会被调用两次。它还假定默认的单例 bean 行为。