最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

java - How to provide a null value for a bean using Mockito and JUnit? - Stack Overflow

programmeradmin0浏览0评论

I'm testing a class which injects an optional Bean and does some special handling when such bean is not available. E.g

@Service
public class FlexibleService {
  @Autowired(required=false)
  private UnreliableService unreliableService;

  doSomething() {
    if (unreliableService == null) {
      // Handle it
    }
  }
}

The question is: how do I setup this scenario in my JUnit and Mockito test case?

So far, this is what I have.

@ExtendWith(MockitoExtension.class)
class FlexibleServiceTest {
  @InjectMocks
  FlexibleService target;

  @Mock
  UnreliableService unreliableService;

  @Test
  void shouldHandleItWhenUnreliableServiceIsNotAvailable() {
    unreliableService = null;

    target.doSomething();

    // expect handling to be made
  }

However, this doesn't work as expected. The bean is injected and my handling code is not reached. How can I force the UnreliableService bean to be provided as null in my test case?

Of course, if the unreliableService was public or had some setter, or even if we had an all args constructor for FlexibleService, we could easily pass unreliableService as null. However, none of these are supposed to happen. Is there any way to work around this?

I'm testing a class which injects an optional Bean and does some special handling when such bean is not available. E.g

@Service
public class FlexibleService {
  @Autowired(required=false)
  private UnreliableService unreliableService;

  doSomething() {
    if (unreliableService == null) {
      // Handle it
    }
  }
}

The question is: how do I setup this scenario in my JUnit and Mockito test case?

So far, this is what I have.

@ExtendWith(MockitoExtension.class)
class FlexibleServiceTest {
  @InjectMocks
  FlexibleService target;

  @Mock
  UnreliableService unreliableService;

  @Test
  void shouldHandleItWhenUnreliableServiceIsNotAvailable() {
    unreliableService = null;

    target.doSomething();

    // expect handling to be made
  }

However, this doesn't work as expected. The bean is injected and my handling code is not reached. How can I force the UnreliableService bean to be provided as null in my test case?

Of course, if the unreliableService was public or had some setter, or even if we had an all args constructor for FlexibleService, we could easily pass unreliableService as null. However, none of these are supposed to happen. Is there any way to work around this?

Share Improve this question edited Jan 30 at 8:33 Mureinik 312k54 gold badges359 silver badges391 bronze badges asked Jan 29 at 19:35 Allan JuanAllan Juan 2,5583 gold badges26 silver badges54 bronze badges 4
  • 1 Switch to constructor injection, it will make your life easier. Constructor injection is the approach recommended by Spring too. – knittl Commented Jan 29 at 20:39
  • What happens if you simply drop @Mock UnreliableService unreliableService; from your class definition? Then there's no mock to inject and the field will remain null. Not sure why you create mock when you specifically want to test for absence? – knittl Commented Jan 29 at 20:40
  • Also note that it's a bit odd that you are trying to explicitly test Spring autowiring without actually using Spring autowiring. @InjectMocks is a Mockito annotation and has nothing to do with Spring dependency injection. – knittl Commented Jan 29 at 20:41
  • just as a side note: you are describing a very common problem with injection and there is a quite easy approach that really helps breaking up the cycle: odrotbohm.de/2013/11/why-field-injection-is-evil – Martin Frank Commented Jan 31 at 7:04
Add a comment  | 

4 Answers 4

Reset to default 3

I think you're overthinking things here - @InjectMocks will inject the respective mocks to all of the class' fields. The easiest way of keeping a field null is just not to provide a mock for it:

@ExtendWith(MockitoExtension.class)
class FlexibleServiceTest {
  @InjectMocks
  FlexibleService target;

  @Test
  void shouldHandleItWhenUnreliableServiceIsNotAvailable() {
    // Since there's no mock for unreliableService, it's implicitly null
    target.doSomething();

    // expect handling to be made
  }
}

Note that this may mean that this test case has its own test class because you do need to mock the unreliable service for other test cases.

As @knittl rightly noted in the comments, it is better to switch to injection via the constructor, because using InjectMocks we cannot change the injected bean. With constructor injection it can look like this:

class FlexibleServiceTest {

    private FlexibleService flexibleService;


    @Test
    void testWhenUnreliableServiceIsPresent() {
        UnreliableService unreliableServiceMock = mock(UnreliableService.class);
        when(unreliableServiceMock.foo()).thenReturn(something);
        
        flexibleService = new FlexibleService(unreliableServiceMock);

        flexibleService.doSomething();
    }

    @Test
    void testWhenUnreliableServiceIsNull() {
        flexibleService = new FlexibleService(null);
        flexibleService.doSomething();
    }
}

To autowire an optional bean via constructor, you can use @Nullable annotation :

import .springframework.lang.Nullable;
import .springframework.stereotype.Service;

@Service
public class FlexibleService {

    private final UnreliableService unreliableService;

    public FlexibleService(@Nullable UnreliableService unreliableService) {
        this.unreliableService = unreliableService;
    }

}

But if you cannot switch to constructor injection, there is an option to inject values via ReflectionTestUtils:

import .springframework.test.util.ReflectionTestUtils;

class FlexibleServiceTest {

    private FlexibleService flexibleService = new FlexibleService();


    @Test
    void testWhenUnreliableServiceIsPresent() {
        UnreliableService unreliableServiceMock = mock(UnreliableService.class);
        
        ReflectionTestUtils.setField(flexibleService, "unreliableService", unreliableServiceMock);

        flexibleService.doSomething();
    }

    @Test
    void testWhenUnreliableServiceIsNull() {
        // unreliableService is null by default
        flexibleService.doSomething();
    }
}

In addition to the other answers, there is also Spring's ReflectionTestUtils which you can use to reach into the class under test and reflectively set fields:

import .springframework.test.util.ReflectionTestUtils;

@Test
void shouldHandleItWhenUnreliableServiceIsNotAvailable() {
    ReflectionTestUtils.setField(target, "unreliableService", null);

    target.doSomething();

    // expect handling to be made
}

Unlike some other approaches, this won't require you to have a separate test class for this one test.

It's unclear why you have Mockito create a Mock instance when you explicitely don't want to have an instance.

If there is no Mock instance of the right type known to Mockito, nothing will be injected and the field will remain null.

@ExtendWith(MockitoExtension.class)
public class FlexibleServiceTest {

    @InjectMocks
    FlexibleService target;

    // don't have Mockito create an UnreliableService Mock instance

    @Test
    void shouldHandleItWhenUnreliableServiceIsNotAvailable() {
        target.doSomething();

        // expect handling to be made
    }
}

In fact, you can even drop the @InjectMocks annotation and get rid of Mockito altogether:

public class FlexibleServiceTest {

    @Test
    void shouldHandleItWhenUnreliableServiceIsNotAvailable() {
        final FlexibleService target = new FlexibleService();
        target.doSomething();

        // expect handling to be made
    }
}
发布评论

评论列表(0)

  1. 暂无评论