-1

I have a @Service bean that I need static access to:

@Service
public class MyBean implements InitializingBean {
    private static MyBean instance;

    @Override
    public void afterPropertiesSet() throws Exception {
        instance = this;
    }

    public static MyBean get() {
        return instance;
    }

    public String someMethod(String param) {
       return "some";
    }
}

Usage:

@Service
public class OtherService {
    public static void makeUse() {
        MyBean myBean = MyBean.get();
    }
}

Problem: when I write an integration junit test for OtherService that makes use of the stat MyBean access, the instance variable is always null.

@RunWith(SpringRunner.class)
@SpringBootTest
public class ITest {
    @Autowired
    private OtherService service;

    @MockBean
    private MyBean myBean;

    @Before
    public void mock() {
        Mockito.when(myBean.someMethod(any()).thenReturn("testvalue");
    }

    @Test
    public void test() {
        service.makeUse(); //NullPointerException, as instance is null in MyBean
    }
}

Question: how can I write an integration test when using such type of static access to a spring-managed bean?

membersound
  • 81,582
  • 193
  • 585
  • 1,120
  • 6
    My first Question is why do you access the bean via a static field when the user of the bean is also spring managed? You can inject the singelton instance by spring – thopaw Dec 13 '17 at 13:49
  • This is just an example. In my code the hierarchy is much deeper, and at some stage I need access to that service inside a static class. To access the `MyBean` always in the same way, I just used the `instance` access everywhere. So maybe I have to rephrase my question in: how to test and mock a static accessed method? – membersound Dec 13 '17 at 13:54
  • 1
    Don't use static singletons!!! They are nasty and evil!!! – lance-java Dec 13 '17 at 13:57
  • You can use [PowerMock](https://github.com/powermock/powermock/wiki/mockstatic) but IMHO you don't need static singeltons – thopaw Dec 13 '17 at 14:00
  • But: imagine I have a static utility method that needs to obtain a variable from a `@Service` class. I cannot inject the service there, so have to provide static access to that service somehow... how could I solve that without static access to the spring-managed service? – membersound Dec 13 '17 at 14:01
  • 3
    If your utility method needs something, then it should/must expect that as a parameter. Accessing a Spring Bean directly from that util method is dirty and broken design. – Tom Dec 13 '17 at 14:05
  • 1
    "imagine I have a static utility method that needs to obtain a variable from a @Service class" Then that utility should NOT be static. It should also be declared in spring and have the service injected – lance-java Dec 13 '17 at 14:10
  • 2
    Ok you convinced me that wanting to mock a static method during testing is probably already a design smell. So I should rewrite my utility to either a) take the service as additional parameter, or b) not being an utility, but a service itself. – membersound Dec 13 '17 at 14:15
  • 3
    Correct. That's one of the great advantages of testing: Code smells are much easier to find as soon as testing becomes strangely complicated. – Florian Schaetz Dec 13 '17 at 14:34

2 Answers2

0

If you want to influence the @Bean-creation, then use @Configuration

@Configuration
public class MyConfig {

    @Bean
    public MyBean myBean() {
        return new MyBean;
    }

    @Bean
    public OtherService otherService () {
        return new OtherService(myBean());
    }
}

Then mocking becomes really easy:

@RunWith(SpringRunner.class)
@SpringBootTest
public class ITest {
    @MockBean
    private MyBean myBean;

    @Autowired
    private OtherService service;

    @Test
    public void test() {
        // service is initialised with myBean
        ...
    }
}
Tom Van Rossom
  • 1,440
  • 10
  • 18
0

When more control is needed, then you can choose the following approach. It provides sometimes more control than just a @MockBean. In your test you can easily mock a method just before calling it. This is my preferred way.

@Configuration
public class MyConfig {
    ...    
    @Bean
    public MyBean getMyBean() {
        return mock( MyBean.class);
    }
    @Bean
    public OtherService otherService () {
        return new OtherService( getMyBean());
    }
}

In the application you can use @Autorwired to access it AND implement method overrule/mocking easily.

@RunWith(SpringRunner.class)
@SpringBootTest
public class AnotherTest {
    @Autowired
    private MyBean myBean;

    @Autowired
    private OtherService service;

    @Test
    public void test() {
        when( myBean.method( a, b)).thenReturn( something);
        service.doSomething( ....); // with myBean.method() overruled 
    }
}
tm1701
  • 7,307
  • 17
  • 79
  • 168