【问题标题】:Testing spring bean with post construct使用 post 构造测试 spring bean
【发布时间】:2015-07-23 12:39:10
【问题描述】:

我有一个类似这样的豆子:

@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...
    }
}

问题是在运行测试时PostConstructsetUp 之前被调用。

【问题讨论】:

  • @hzpz A 类有其他逻辑,后者在测试中调用。并回答您的问题,我想测试 A 类的逻辑。

标签: spring junit postconstruct


【解决方案1】:

如果您想编写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);
    }
}

-

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。

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

@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 public class Tests { @Bean public B b() { B b = Mockito.mock(B.class); Mockito.when(b).thenReturn("smth"); return b; } }
  • 解决方案的第一部分是通过终止测试的“集成”部分来避免真正的问题。第二部分好多了
  • @cahen 这就是为什么我从“如果你想写一个 unit 测试”开始。根据我的经验,当人们真正想要进行单元测试时,他们往往倾向于编写集成测试。
  • @Andy 我根据你的评论更新了我的答案。
  • 在解决方案的第一部分中,您避免了问题,在解决方案的第二部分中,您还通过不使用 @Service 注释来避免问题
【解决方案2】:

刚刚在我正在处理的项目中遇到了这个确切的问题,这是我在问题代码方面使用的解决方案:

  1. @Autowire 在带有 @PostConstruct 的 bean 中进行测试。
  2. @Before 中进行设置。
  3. @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 行为。

【讨论】:

    【解决方案3】:

    另一种选择是自己在测试中实例化应用程序上下文,然后在刷新上下文之前注入模拟,例如:

    @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 注释并且您想在模拟之前设置期望时,此替代方法很有用。

    【讨论】:

      猜你喜欢
      • 2019-03-07
      • 2013-05-26
      • 2019-07-18
      • 1970-01-01
      • 2023-03-17
      • 2014-05-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多