I have a bean similar to this:
@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...
}
}
The problem is that PostConstruct
is called before setUp
when the test is run.
Just had this exact problem on a project I'm working on, Here is the solution I used in terms of the question code:
@Autowire
in the bean with the @PostConstruct
to your test.@Before
.@PostConstruct
at the end of your @Before
.@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...
}
}
Obviously your @PostConstruct
method has to be idempotent as its going to get called twice. Also it assumes default singleton bean behaviour.
If you want to write a unit test of A
, then don't use Spring. Instead, instantiate A
yourself and pass a stub/mock of B
(either by using constructor injection or ReflectionTestUtils
to set the private field).
For example:
@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
}
}
If you have to use Spring, because you want to write an integration test, use a different application context, where you replace B
with a stub/mock.
For example, assuming B
is instantiated in a Production
class like this:
@Configuration
public class Production {
@Bean
public B b() {
return new B();
}
}
Write another @Configuration
class for your tests:
@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;
}
}
Reference it in your test with the @SpringApplicationConfiguration
annotation:
@SpringApplicationConfiguration(classes = { Application.class, Tests.class })
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With