用post构造测试springbean

用post构造测试springbean,spring,junit,postconstruct,Spring,Junit,Postconstruct,我有一个类似的bean: @Service public class A { @Autowired private B b; @PostConstruct public void setup() { b.call(param); } } @RunWith(SpringJUnit4ClassRunner.class) @SpringApplicationConfiguration(classes = { Application.class

我有一个类似的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...
    }
}

问题是运行测试时,在
setUp
之前调用
PostConstruct

如果要编写
a
单元测试,则不要使用Spring。相反,自己实例化
A
并传递
B
的存根/模拟(通过使用构造函数注入或
ReflectionTestUtils
设置私有字段)

例如:

@Service
public class A {

    private final B b;    

    @Autowired
    public A(B b) {
        this.b = b;
    }

    @PostConstruct
    public void setup() {
       b.call(param);
    }
}
-

如果您必须使用Spring,因为您想编写一个集成测试,请使用不同的应用程序上下文,在该上下文中用存根/模拟替换
B

例如,假设
B
Production
类中实例化,如下所示:

@Configuration
public class Production {

    @Bean
    public B b() {
        return new B();
    }

}
为测试编写另一个
@Configuration
类:

@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
注释,并且您希望在模拟之前设置期望值时,此备选方案非常有用。

我正在处理的一个项目正好遇到了这个问题,以下是我在问题代码方面使用的解决方案:

  • bean中的
    @Autowire
    ,将
    @PostConstruct
    添加到测试中
  • 在之前的
    @中进行设置
  • 显式调用
    @PostConstruct
    之前的
    @末尾的
    @PostConstruct

  • 显然,您的
    @PostConstruct
    方法必须是幂等的,因为它将被调用两次。它还假设默认的单例bean行为。

    @hzpz类A有其他逻辑,这些逻辑在后面的测试中调用。回答你们的问题,我想测试一下A班的逻辑。谢谢!是的,几乎是对的。另外,
    @Configuration公共类测试{@Bean public B B(){B B B=Mockito.mock(B.class);Mockito.when(B).thenReturn(“smth”);return B;}
    解决方案的第一部分是通过终止测试的“集成”部分来避免真正的问题。第二部分很重要better@cahen这就是为什么我从“如果你想写一个单元测试”开始。根据我的经验,人们通常倾向于在实际需要单元测试时编写集成测试。@Andy我根据您的评论更新了我的答案。在解决方案的第一部分中,您避免了问题,在解决方案的第二部分中,您也通过不使用@Service annotation来避免问题
    @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();
    
    @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...
        }
    }