88

I've got a Spring component I'd like to test and this component has an autowired attribute which I need to change for the purpose of unit testing. The problem is, that the class uses the autowired component inside the post-construct method so I'm not able to replace it(i.e. via ReflectionTestUtils) before it's actually used.

How should I do that?

This is the class I want to test:

@Component
public final class TestedClass{

    @Autowired
    private Resource resource;

    @PostConstruct
    private void init(){
        //I need this to return different result
        resource.getSomething();
    }
}

And this is the base of a test case:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations= "classpath:applicationContext.xml")
public class TestedClassTest{

    @Autowired
    private TestedClass instance;

    @Before
    private void setUp(){
        //this doesn't work because it's executed after the bean is instantiated
        ReflectionTestUtils.setField(instance, "resource", new Resource("something"));
    }
}

Is there some way to replace the resource with something else before the postconstruct method is invoked? Like to tell Spring JUnit runner to autowire different instance?

Crowie
  • 3,220
  • 7
  • 28
  • 48
NeplatnyUdaj
  • 6,052
  • 6
  • 43
  • 76
  • Depends on the Spring version you are using - there might be different answers. Refer to: https://dzone.com/articles/mockbean-spring-boots-missing-ingredient – Witold Kaczurba Feb 05 '18 at 07:25

8 Answers8

74

You could use Mockito. I am not sure with PostConstruct specifically, but this generally works:

// Create a mock of Resource to change its behaviour for testing
@Mock
private Resource resource;

// Testing instance, mocked `resource` should be injected here 
@InjectMocks
@Resource
private TestedClass testedClass;

@Before
public void setUp() throws Exception {
    // Initialize mocks created above
    MockitoAnnotations.initMocks(this);
    // Change behaviour of `resource`
    when(resource.getSomething()).thenReturn("Foo");   
}
Mark F Guerra
  • 892
  • 10
  • 13
Anna Zubenko
  • 1,645
  • 2
  • 11
  • 12
31

Spring Boot 1.4 introduced testing annotation called @MockBean. So now mocking and spying on Spring beans is natively supported by Spring Boot.

luboskrnac
  • 23,973
  • 10
  • 81
  • 92
15

You can provide a new testContext.xml in which the @Autowired bean you define is of the type you need for your test.

phury
  • 2,123
  • 2
  • 21
  • 33
  • 1
    Thanks. That's exactly what I did and is working. I'm just wondering if there's a way to do that without writing new context(although I only imported the old one and overrode the bean i needed) – NeplatnyUdaj Oct 10 '13 at 15:39
  • 1
    @NeplatnyUdaj try using the @Qualifier("myAlternateBean") then, it should solve your problem – phury Oct 10 '13 at 15:56
  • That's a misunderstanding. Your suggestion works. It's just not exactly what I was looking for. I'll use it until I find something else. Thanks – NeplatnyUdaj Oct 10 '13 at 16:42
  • If you are using autowiring in tests then having test specific contexts is normal so I wouldn't worry about looking for something better. The only other possibilities are to override init (which means it can't be private) or manually wire your beans for the test. – matt helliwell Oct 12 '13 at 20:43
  • @ph. How to do it in Spring-Boot where there are no .xml files. Do I have to create a configuration file for the test? – Tarun Maganti Mar 15 '17 at 11:45
9

I created blog post on the topic. It contains also link to Github repository with working example.

The trick is using test configuration, where you override original spring bean with fake one. You can use @Primary and @Profile annotations for this trick.

luboskrnac
  • 23,973
  • 10
  • 81
  • 92
8

You can override bean definitions with mocks with spring-reinject https://github.com/sgri/spring-reinject/

David Newcomb
  • 10,639
  • 3
  • 49
  • 62
Sergey Grigoriev
  • 709
  • 7
  • 15
5

Another approach in integration testing is to define a new Configuration class and provide it as your @ContextConfiguration. Into the configuration you will be able to mock your beans and also you must define all types of beans which you are using in test/s flow. To provide an example :

@RunWith(SpringRunner.class)
@ContextConfiguration(loader = AnnotationConfigContextLoader.class)
public class MockTest{
 @Configuration
 static class ContextConfiguration{
 // ... you beans here used in test flow
 @Bean
    public MockMvc mockMvc() {
        return MockMvcBuilders.standaloneSetup(/*you can declare your controller beans defines on top*/)
                .addFilters(/*optionally filters*/).build();
    }
 //Defined a mocked bean
 @Bean
    public MyService myMockedService() {
        return Mockito.mock(MyService.class);
    }
 }

 @Autowired
 private MockMvc mockMvc;

 @Autowired
 MyService myMockedService;

 @Before
 public void setup(){
  //mock your methods from MyService bean 
  when(myMockedService.myMethod(/*params*/)).thenReturn(/*my answer*/);
 }

 @Test
 public void test(){
  //test your controller which trigger the method from MyService
  MvcResult result = mockMvc.perform(get(CONTROLLER_URL)).andReturn();
  // do your asserts to verify
 }
}
RazvanParautiu
  • 2,805
  • 2
  • 18
  • 21
2

For Junit5, you can mock using:

@ExtendWith(MockitoExtension.class)
@MockitoSettings(strictness = Strictness.LENIENT)
class MytestClass {

@Mock
MyInjectedSevice myInjservice;

@InjectMock
MyService myservice;

}
Mark Rotteveel
  • 100,966
  • 191
  • 140
  • 197
Smart Coder
  • 1,435
  • 19
  • 19
0
import org.junit.Before;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;

import javax.annotation.Resource;

@Mock
    private IMyInterface yInterface;

    @InjectMocks
    @Resource
    ResourceDependant resourceDependant = new resourceDependant();


    @Before
    public void initMocksForInjection() throws Exception {
        MockitoAnnotations.openMocks(this);
    }