Mockito - 当进行方法存根时出现NullpointerException异常

183

因此,我开始为我们的Java-Spring项目编写测试。

我使用的是JUnit和Mockito。据说,当我使用when()...thenReturn()选项时,我可以模拟服务,而不需要像模拟它们一样。因此,我的目标是设置:

when(classIwantToTest.object.get().methodWhichReturnsAList(input))thenReturn(ListcreatedInsideTheTestClass)  
无论我使用哪个when语句,都会得到一个NullpointerException异常,这当然是有道理的,因为输入是null。
还有,当我尝试模拟对象的另一个方法时:
when(object.method()).thenReturn(true)

因为该方法需要一个未设置的变量,所以我也会遇到空指针异常。

但是我想使用 when()..thenReturn() 来避免创建这个变量等操作。我只想确保如果任何类调用此方法,则无论如何都返回 true 或上面的列表。

这是我基本的误解,还是有其他问题?

代码:

public class classIWantToTest implements classIWantToTestFacade{
        @Autowired
        private SomeService myService;

        @Override
        public Optional<OutputData> getInformations(final InputData inputData) {
            final Optional<OutputData> data = myService.getListWithData(inputData);
            if (data.isPresent()) {
                final List<ItemData> allData = data.get().getItemDatas();
                    //do something with the data and allData
                return data;
            }

            return Optional.absent();
        }   
}

这是我的测试类:

public class Test {

    private InputData inputdata;

    private ClassUnderTest classUnderTest;

    final List<ItemData> allData = new ArrayList<ItemData>();

    @Mock
    private DeliveryItemData item1;

    @Mock
    private DeliveryItemData item2;



    @Mock
    private SomeService myService;


    @Before
    public void setUp() throws Exception {
        classUnderTest = new ClassUnderTest();
        myService = mock(myService.class); 
        classUnderTest.setService(myService);
        item1 = mock(DeliveryItemData.class);
        item2 = mock(DeliveryItemData.class);

    }


    @Test
    public void test_sort() {
        createData();
        when(myService.getListWithData(inputdata).get().getItemDatas());

        when(item1.hasSomething()).thenReturn(true);
        when(item2.hasSomething()).thenReturn(false);

    }

    public void createData() {
        item1.setSomeValue("val");
        item2.setSomeOtherValue("test");

        item2.setSomeValue("val");
        item2.setSomeOtherValue("value");

        allData.add(item1);
        allData.add(item2);


}

6
在 when() 中使用的对象必须是由 Mockito.mock() 创建的模拟对象,无法用手动创建的真实对象 - 不确定这是否与您的问题有关,因为我不太清楚您的问题在哪里... - Nadir
1
听起来像是classIwantToTest.object.get()的结果不是一个模拟对象,或者get()返回了null。但请发布您的代码,显示您的测试和模拟对象的初始化。 - vikingsteve
添加了一些代码。需要更改变量名称,因为这是来自我的公司;)。 - user5417542
1
访问此问题的帮助:确保您的方法签名中没有意外地使用了 final。这是另一个难以追踪的空指针异常的原因。 - 8bitjunkie
请创建一个最小化的示例: https://stackoverflow.com/help/minimal-reproducible-example - john k
33个回答

360

我也遇到了这个问题,原因是我在调用方法时使用了 any() 而不是 anyInt()。所以我的代码是:

doAnswer(...).with(myMockObject).thisFuncTakesAnInt(any())

然后我不得不进行更改:

doAnswer(...).with(myMockObject).thisFuncTakesAnInt(anyInt())

我不知道为什么会出现NullPointerException。也许这能帮助下一个可怜的人。


94
这对我有用。为了下一个可怜的灵魂,留下这个评论。 - Pavan Kumar
90
如果参数是原始类型,匹配器将不起作用,因此您需要使用anyChar/Int/Boolean等。 - micah
51
禁止 any() 对所有变量起作用,并将其表现为 NPE,等于对开发团队发动网络战攻击。 - haventchecked
8
原因是 any() 返回一个通用的对象引用。该对象被拆箱为整数,因此确定其通用类型为“整数”。然而该对象为空,因此拆箱操作访问了空指针导致失败。而 anyInt() 则提供了一个本地整数,所以能够正常工作。 - Edward
3
这对我有用。留下这个评论给下一个可怜的灵魂。 ......五年过去了...... 我就是那个可怜的灵魂哈哈。 - Bing Ren
显示剩余7条评论

89

你还没有存根化的方法默认返回值为布尔方法的 false,集合或映射方法的返回值为一个空集合或映射对象, 否则返回值为null

这也适用于when(...)内部的方法调用。例如,在你的示例中,when(myService.getListWithData(inputData).get())将导致NullPointerException,因为myService.getListWithData(inputData)尚未被存根化。

一种解决方法是为所有中间返回值创建模拟对象并在使用之前对它们进行存根化。例如:

ListWithData listWithData = mock(ListWithData.class);
when(listWithData.get()).thenReturn(item1);
when(myService.getListWithData()).thenReturn(listWithData);

或者,您可以在创建模拟对象时指定不同的默认答案,以使方法返回一个新的模拟对象而不是 null:RETURNS_DEEP_STUBS

SomeService myService = mock(SomeService.class, Mockito.RETURNS_DEEP_STUBS);
when(myService.getListWithData().get()).thenReturn(item1);

你应该阅读Mockito.RETURNS_DEEP_STUBS的Javadoc文档,其中更详细地解释了这个方法,并且还对其使用提出了一些警告。

希望这能帮助到你。请注意,你的示例代码似乎还有其他问题,比如缺少assert或verify语句以及在mock对象上调用setter(这没有任何效果)。


我在哪里可以找到第一个回答的规范/文档?谢谢! - Avery Sturzl
3
请查看以下链接:https://github.com/mockito/mockito/blob/v2.20.0/src/main/java/org/mockito/internal/stubbing/defaultanswers/ReturnsEmptyValues.java#L20-L49 或者访问 https://static.javadoc.io/org.mockito/mockito-core/2.20.0/org/mockito/Mockito.html#RETURNS_DEFAULTS 以获取更多信息。 - ahu

85

我遇到了同样的问题,我的问题很简单,就是我没有使用 @RunWith 正确地注释类。在你的例子中,请确保你已经:

@RunWith(MockitoJUnitRunner.class)
public class Test {
...

我这样做后,NullPointerExceptions就消失了。


2
我因为@RunWith(PowerMockRunner.class)得到了空指针,所以我将其更改为@RunWith(MockitoJUnitRunner.class)。 - anand krish
对于 Kotlin:@RunWith(MockitoJUnitRunner::class) - Ravi Yadav
2
遇到了使用Junit 5的同样问题,我使用@ExtendWith(MockitoExtension.class)来替代@RunWith(MockitoJUnitRunner.class)解决了这个问题。 - Yacino

34

对于未来的读者,使用模拟对象时导致NPE的另一个原因是忘记像这样初始化模拟对象:

@Mock
SomeMock someMock;

@InjectMocks
SomeService someService;

@Before
public void setup(){
    MockitoAnnotations.initMocks(this); //without this you will get NPE
}

@Test
public void someTest(){
    Mockito.when(someMock.someMethod()).thenReturn("some result");
   // ...
}

同时确保你在所有注解中都使用JUnit。我曾经不小心使用了testNG的@Test创建了一个测试,导致@Before不能正常工作(testNG中的注解是@BeforeTest)。


1
@BeforeEach 在这里帮了我,但它总是取决于执行上下文。 - Mateusz Gebroski
5
支持“确保您在所有注释中都使用JUnit”!某种方式,Intellij假定我想要使用org.junit.jupiter.api.Test而不是import org.junit.Test,这导致模拟对象显然为空。 - Sayadi
我不知何故错过了这行代码 "MockitoAnnotations.initMocks(this); "。 - Ajay Kedare
在我的情况下,我忘记有效地初始化我的变量。谢谢! - anonymous

29

我的原因是在模拟基本数据类型时使用了Mockito.any(),导致出现了NPE。后来我发现通过使用mockito的正确变量来替换它可以消除这些错误。

例如,要模拟一个需要传递基本类型long参数的函数,你应该更具体地使用any(Long.class)或者Mockito.anyLong()替代any()

希望对某些人有所帮助。


2
谢谢!!!我一直在遇到一些毫无意义的错误,但是使用Mockito.anyInt()而不是Mockito.any()解决了它。例如错误:org.mockito.exceptions.misusing.InvalidUseOfMatchersException: Misplaced or misused argument matcher detected... You cannot use argument matchers outside of verification or stubbing. - Aaron
谢谢!我使用了anyString()和anyBoolean()代替any(),测试通过了。非常感谢。 - Prashant Singh

20

由于这是我发现的最接近我遇到的问题,它是出现的第一个结果,并且我没有找到适当的答案,所以我将在这里发布解决方案,以供未来的可怜之人使用:

any() 在模拟的类方法使用原始参数时无法正常工作。

 public Boolean getResult(String identifier, boolean switch)

上述操作会产生与 OP 完全相同的问题。

解决方案,只需将其包装:

 public Boolean getResult(String identifier, Boolean switch)

后者解决了NPE。

  • 请记住,如果您选择此方法,现在可能需要在生产代码中包含对Boolean的nullcheck(来源:Ridcully提出)

2
那正是发生在我身上的事情。 - Felipe Desiderati
1
寻找和解决方案 - Saurabh Gupta
1
那么要修复测试中的错误/漏洞,您必须更改生产代码吗?我认为这不是正确的方法。实际上,您只需允许空值作为开关的值,因此您现在必须在方法中实现所有开关为空的情况。 - Ridcully
这是一个不错的评论,Ridcully。我会添加那个注释。谢谢。 - Tanel
1
或者更好的是,你可以将any()替换为anyBoolean()并在代码中保留原始类型... - Ilario
好了,点赞了 :) - Tanel

15

请确保初始化您的模拟对象。

JUnit4 使用 @Before

@Before
public void setup() {
    MockitoAnnotations.initMocks(this);
}

JUnit5 使用 @BeforeEach

@BeforeEach
public void setup() {
    MockitoAnnotations.initMocks(this);
}

对于 JUnit5 的检查,您还使用了适当的导入。

import org.junit.runner.RunWith
import org.mockito.junit.MockitoJUnitRunner;

@RunWith(MockitoJUnitRunner.class)

initMocks已经被弃用,请使用MockitoAnnotations.openMocks代替。 - Duminda Wanninayake
我正在使用JUnit 4版本,但仍需要"@RunWith(MockitoJUnitRunner.class)"注释才能使"when"方法正常工作。 - Saurabh Gupta

8

边角案例:
如果您正在使用Scala,并且尝试在值类上创建any匹配器,则会收到一个不太有用的NPE。

因此,假设有case class ValueClass(value: Int) extends AnyVal,您想要做的是使用ValueClass(anyInt)而不是any[ValueClass]

when(mock.someMethod(ValueClass(anyInt))).thenAnswer {
   ...
   val v  = ValueClass(invocation.getArguments()(0).asInstanceOf[Int])
   ...
}

这个Stack Overflow问题更具体地涉及到这个问题,但如果你不知道问题是与值类有关的,你就会错过它。


7

对于JUnit 5,测试类必须带有以下注释:

@ExtendWith(MockitoExtension.class)

导入:

import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.junit.jupiter.MockitoExtension;

通过这个补充,我的问题得以解决。


6

在使用Mockito进行单元测试时,你需要调用MockitoAnnotations.initMocks(this)方法来初始化被注释的字段。

   @Before public void initMocks() {
       MockitoAnnotations.initMocks(this);
   }

更多详细信息请参见文档


使用 org.junit.jupiter.api.BeforeEach 取代 @Before 对我很有帮助。 - RAM237

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接