简体   繁体   English

Mockito - 存根方法时出现 NullpointerException

[英]Mockito - NullpointerException when stubbing Method

So I started writing tests for our Java-Spring-project.所以我开始为我们的 Java-Spring-项目编写测试。

What I use is JUnit and Mockito. It's said, that when I use the when()...thenReturn() option I can mock services, without simulating them or so.我使用的是 JUnit 和 Mockito。据说,当我使用 when()...thenReturn() 选项时,我可以模拟服务,而无需模拟它们。 So what I want to do is, to set:所以我想做的是设置:

when(classIwantToTest.object.get().methodWhichReturnsAList(input))thenReturn(ListcreatedInsideTheTestClass)  

But no matter which when-clause I do, I always get a NullpointerException, which of course makes sense, because input is null.但无论我执行哪个 when 子句,我总是得到 NullpointerException,这当然是有道理的,因为输入是 null。

Also when I try to mock another method from an object:此外,当我尝试从 object 模拟另一种方法时:

when(object.method()).thenReturn(true)

There I also get a Nullpointer, because the method needs a variable, which isn't set.在那里我也得到一个 Nullpointer,因为该方法需要一个未设置的变量。

But I want to use when()..thenReturn() to get around creating this variable and so on.但是我想使用 when()..thenReturn() 来绕过创建这个变量等等。 I just want to make sure, that if any class calls this method, then no matter what, just return true or the list above.我只是想确保,如果任何 class 调用此方法,那么无论如何,只需返回 true 或上面的列表。

Is it a basically misunderstanding from my side, or is there something else wrong?这基本上是我这边的误解,还是有其他问题?

Code:代码:

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();
        }   
}

And here is my test class:这是我的测试 class:

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);


}

I had this issue and my problem was that I was calling my method with any() instead of anyInt() .我遇到了这个问题,我的问题是我使用any()而不是anyInt()调用我的方法。 So I had:所以我有:

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

and I had to change it to:我不得不将其更改为:

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

I have no idea why that produced a NullPointerException.我不知道为什么会产生 NullPointerException。 Maybe this will help the next poor soul.也许这会帮助下一个可怜的灵魂。

The default return value of methods you haven't stubbed yet is false for boolean methods, an empty collection or map for methods returning collections or maps and null otherwise.对于布尔方法,尚未存根的方法的默认返回值为false ,对于返回集合或映射的方法,返回空集合或映射,否则为null

This also applies to method calls within when(...) .这也适用于when(...)中的方法调用。 In you're example when(myService.getListWithData(inputData).get()) will cause a NullPointerException because myService.getListWithData(inputData) is null - it has not been stubbed before.在您的示例中when(myService.getListWithData(inputData).get())将导致 NullPointerException 因为myService.getListWithData(inputData)null - 它之前没有被存根。

One option is create mocks for all intermediate return values and stub them before use.一种选择是为所有中间返回值创建模拟,并在使用前存根它们。 For example:例如:

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

Or alternatively, you can specify a different default answer when creating a mock, to make methods return a new mock instead of null: RETURNS_DEEP_STUBS或者,您可以在创建模拟时指定不同的默认答案,以使方法返回新模拟而不是 null: RETURNS_DEEP_STUBS

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

You should read the Javadoc of Mockito.RETURNS_DEEP_STUBS which explains this in more detail and also has some warnings about its usage.您应该阅读Mockito.RETURNS_DEEP_STUBS的 Javadoc,它更详细地解释了这一点,并且还有一些关于其使用的警告。

I hope this helps.我希望这有帮助。 Just note that your example code seems to have more issues, such as missing assert or verify statements and calling setters on mocks (which does not have any effect).请注意,您的示例代码似乎有更多问题,例如缺少断言或验证语句以及在模拟上调用设置器(这没有任何效果)。

I had the same problem and my issue was simply that I had not annotated the class properly using @RunWith.我遇到了同样的问题,我的问题只是我没有使用 @RunWith 正确注释类。 In your example, make sure that you have:在您的示例中,请确保您拥有:

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

Once I did that, the NullPointerExceptions disappeared.一旦我这样做了,NullPointerExceptions 就消失了。

For future readers, another cause for NPE when using mocks is forgetting to initialize the mocks like so:对于未来的读者来说,使用模拟时 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");
   // ...
}

Also make sure you are using JUnit for all annotations.还要确保所有注释都使用 JUnit。 I once accidently created a test with @Test from testNG so the @Before didn't work with it (in testNG the annotation is @BeforeTest)我曾经不小心用来自 testNG 的 @Test 创建了一个测试,所以 @Before 不能使用它(在 testNG 中,注释是 @BeforeTest)

For me the reason I was getting NPE is that I was using Mockito.any() when mocking primitives.对我来说,我获得 NPE 的原因是我在模拟原语时使用Mockito.any() I found that by switching to using the correct variant from mockito gets rid of the errors.我发现通过切换到使用来自 mockito 的正确变体可以消除错误。

For example, to mock a function that takes a primitive long as parameter, instead of using any() , you should be more specific and replace that with any(Long.class) or Mockito.anyLong() .例如,要模拟一个将原始long作为参数的函数,而不是使用any() ,您应该更具体并将其替换为any(Long.class)Mockito.anyLong()

Hope that helps someone.希望对某人有所帮助。

Make sure you initialize your mocks.确保你初始化你的模拟。

JUnit4 use @Before JUnit4使用@Before

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

JUnit5 use @BeforeEach JUnit5使用@BeforeEach

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

For JUnit5 check, you are using proper imports also.对于JUnit5检查,您也在使用正确的导入。

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

@RunWith(MockitoJUnitRunner.class)

As this is the closest I found to the issue I had, it's the first result that comes up and I didn't find an appropriate answer, I'll post the solution here for any future poor souls:由于这是我发现的与我遇到的问题最接近的结果,因此这是出现的第一个结果,但我没有找到合适的答案,我将在此处为任何未来的可怜人发布解决方案:

any() doesn't work where mocked class method uses a primitive parameter. any()在模拟类方法使用原始参数的情况下不起作用。

 public Boolean getResult(String identifier, boolean switch)

The above will produce the same exact issue as OP.以上将产生与OP相同的问题。

Solution, just wrap it:解决方案,只需包装它:

 public Boolean getResult(String identifier, Boolean switch)

The latter solves the NPE.后者解决了NPE。

  • keep in mind if you choose this approach, now you might want to include a nullcheck for Boolean in production code (credit: brought up by Ridcully)请记住,如果您选择这种方法,现在您可能希望在生产代码中包含布尔值的空检查(信用:由 Ridcully 提出)

Corner case:角落案例:
If you're using Scala and you try to create an any matcher on a value class , you'll get an unhelpful NPE.如果您使用Scala并尝试在value class上创建any匹配器,您将得到一个无用的 NPE。

So given case class ValueClass(value: Int) extends AnyVal , what you want to do is ValueClass(anyInt) instead of any[ValueClass]所以给定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])
   ...
}

This other SO question is more specifically about that, but you'd miss it when you don't know the issue is with value classes.另一个SO 问题更具体地说明了这一点,但是当您不知道问题出在值类上时,您会错过它。

you need to initialize MockitoAnnotations.initMocks(this) method has to called to initialize annotated fields.您需要初始化 MockitoAnnotations.initMocks(this) 方法必须调用以初始化带注释的字段。

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

for more details see Doc有关更多详细信息,请参阅文档

For JUnit 5 the test class has to be annotated with:对于 JUnit 5,测试类必须使用以下注释:

@ExtendWith(MockitoExtension.class)

imports:进口:

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

My issue was fixed with this addition.我的问题已通过此添加解决。

Another common gotcha is that the method signature is accidentally declared as 'final'.另一个常见的问题是方法签名被意外声明为“final”。

This one catches out a lot of people who work on codebases which are subjected to Checkstyle and have internalised the need to mark members as final .这吸引了很多从事代码库工作的人,这些代码库受到 Checkstyle 的影响,并内化了将成员标记为final的需求。

ie in the OP's example:即在OP的例子中:

object.method()

Make sure that method() is not declared as final :确保method()未声明为final

public final Object method() {
}

Mockito cannot mock a final method and this will come up as a wrapped NPE: Mockito 无法模拟最终方法,这将作为一个包装的 NPE 出现:

Suppressed: org.mockito.exceptions.misusing.InvalidUseOfMatchersException:

Buried deep in the error message is the following:深埋在错误消息中的是以下内容:

Also, this error might show up because you use argument matchers with methods that cannot be mocked.
Following methods *cannot* be stubbed/verified: final/private/equals()/hashCode().
Mocking methods declared on non-public parent classes is not supported.

For me, it was because I was stubbing the mock in the @BeforeAll method.对我来说,这是因为我在@BeforeAll方法中存根模拟。

MockitoExtension does not have a callback for @BeforeAll . MockitoExtension没有@BeforeAll的回调。

public class MockitoExtension implements BeforeEachCallback, AfterEachCallback, ParameterResolver

I moved the stubbing inside the test method it worked!!我在它工作的测试方法中移动了存根!

In my case, Intellij created Test with org.junit.jupiter.api.Test (Junit5) instead of import org.junit.Test of (Junit4) which caused all beans to be null apparently.就我而言,Intellij 使用org.junit.jupiter.api.Test (Junit5) 而不是 import org.junit.Test of (Junit4) 创建了 Test,这导致所有 bean 显然都为空。 also, make sure the class and test method is public另外,确保类和测试方法是公开的

@RunWith(MockitoJUnitRunner.class) //(OR) PowerMockRunner.class

@PrepareForTest({UpdateUtil.class,Log.class,SharedPreferences.class,SharedPreferences.Editor.class})
public class InstallationTest extends TestCase{

@Mock
Context mockContext;
@Mock
SharedPreferences mSharedPreferences;
@Mock
SharedPreferences.Editor mSharedPreferenceEdtor;

@Before
public void setUp() throws Exception
{
//        mockContext = Mockito.mock(Context.class);
//        mSharedPreferences = Mockito.mock(SharedPreferences.class);
//        mSharedPreferenceEdtor = Mockito.mock(SharedPreferences.Editor.class);
    when(mockContext.getSharedPreferences(Mockito.anyString(),Mockito.anyInt())).thenReturn(mSharedPreferences);
    when(mSharedPreferences.edit()).thenReturn(mSharedPreferenceEdtor);
    when(mSharedPreferenceEdtor.remove(Mockito.anyString())).thenReturn(mSharedPreferenceEdtor);
    when(mSharedPreferenceEdtor.putString(Mockito.anyString(),Mockito.anyString())).thenReturn(mSharedPreferenceEdtor);
}

@Test
public void deletePreferencesTest() throws Exception {

 }
}

All the above commented codes are not required { mockContext = Mockito.mock(Context.class);以上所有注释代码都不是必需的 { mockContext = Mockito.mock(Context.class); }, if you use @Mock Annotation to Context mockContext; },如果你使用@Mock Annotation to Context mockContext;

@Mock 
Context mockContext; 

But it will work if you use @RunWith(MockitoJUnitRunner.class) only.但是,如果您仅使用@RunWith(MockitoJUnitRunner.class)它将起作用。 As per Mockito you can create mock object by either using @Mock or Mockito.mock(Context.class);根据 Mockito,您可以使用@Mock 或 Mockito.mock(Context.class);创建模拟对象。 , ,

I got NullpointerException because of using @RunWith(PowerMockRunner.class), instead of that I changed to @RunWith(MockitoJUnitRunner.class) it works fine由于使用了@RunWith(PowerMockRunner.class),我得到了 NullpointerException,而不是我改为@RunWith(MockitoJUnitRunner.class) 它工作正常

In my case, I missed add first就我而言,我错过了 add first

PowerMockito.spy(ClassWhichNeedToBeStaticMocked.class);

so this can be helpful to somebody who see such error所以这对看到此类错误的人会有所帮助

java.lang.NullPointerException
    at org.powermock.api.mockito.internal.expectation.PowerMockitoStubberImpl.addAnswersForStubbing(PowerMockitoStubberImpl.java:67)
    at org.powermock.api.mockito.internal.expectation.PowerMockitoStubberImpl.when(PowerMockitoStubberImpl.java:42)
    at org.powermock.api.mockito.internal.expectation.PowerMockitoStubberImpl.when(PowerMockitoStubberImpl.java:112)

None of the above answers helped me.以上答案都没有帮助我。 I was struggling to understand why code works in Java but not in Kotlin.我一直在努力理解为什么代码在 Java 中有效,但在 Kotlin 中无效

Then I figured it out from this thread .然后我从这个线程中弄清楚了。

You have to make class and member functions open , otherwise NPE was being thrown.您必须使类和成员函数open ,否则会抛出 NPE。

After making function open tests started to pass.使功能open后测试开始通过。

You might as well consider using compiler's "all-open" plugin :您不妨考虑使用编译器的“全开放”插件

Kotlin has classes and their members final by default , which makes it inconvenient to use frameworks and libraries such as Spring AOP that require classes to be open . Kotlin默认具有类及其成员 final ,这使得使用 Spring AOP 等要求类是开放的框架和库很不方便。 The all-open compiler plugin adapts Kotlin to the requirements of those frameworks and makes classes annotated with a specific annotation and their members open without the explicit open keyword. all-open编译器插件使 Kotlin 适应这些框架的要求,并使带有特定注释的类及其成员在没有显式 open 关键字的情况下打开。

Well in my case it was because of wrong annotation usage.那么在我的情况下,这是因为错误的注释使用。 I was using junit 4 for testing and used @BeforeEach instead of @Before while initializing.我使用 junit 4 进行测试,并在初始化时使用@BeforeEach而不是@Before

Changed it to @Before and it works like charm.将其更改为@Before ,它就像魅力一样。

In my case, it was the wrong import for when() .就我而言, when()的导入错误。

I used import static reactor.core.publisher.Mono.when by accident.我偶然使用了import static reactor.core.publisher.Mono.when

Ed Webb's answer helped in my case. Ed Webb 的回答对我有帮助。 And instead, you can also try add相反,您也可以尝试添加

  @Rule public Mocks mocks = new Mocks(this);

if you @RunWith(JUnit4.class) .如果你@RunWith(JUnit4.class)

None of these answers worked for me.这些答案都不适合我。 This answer doesn't solve OP's issue but since this post is the only one that shows up on googling this issue, I'm sharing my answer here.这个答案并不能解决 OP 的问题,但由于这篇文章是唯一出现在谷歌搜索这个问题的帖子,所以我在这里分享我的答案。

I came across this issue while writing unit tests for Android.我在为 Android 编写单元测试时遇到了这个问题。 The issue was that the activity that I was testing extended AppCompatActivity instead of Activity .问题是我正在测试的活动扩展了AppCompatActivity而不是Activity To fix this, I was able to just replace AppCompatActivity with Activity since I didn't really need it.为了解决这个问题,我可以将AppCompatActivity替换为Activity ,因为我真的不需要它。 This might not be a viable solution for everyone, but hopefully knowing the root cause will help someone.这对每个人来说可能不是一个可行的解决方案,但希望知道根本原因会对某人有所帮助。

When using JUnit 5 or above.使用 JUnit 5 或更高版本时。 You have to inject the class annotated with @Mock in an @BeforeEach setup.您必须在@BeforeEach设置中注入带有@Mock注释的类。

In my case it was due to wrong import of the @Test annotation在我的情况下,这是由于 @Test 注释的错误导入

Make sure you are using the following import确保您使用的是以下导入

import org.junit.jupiter.api.Test;

使用@ExtendWith(MockitoExtension.class)注释测试类。

This is where google took me when I had the same NullPointerException with Junit 5, but was correctly using @ExtendWith(MockitoExtension.class) in my maven project.当我在 Junit 5 中遇到相同的NullPointerException ,但在我的 maven 项目中正确使用@ExtendWith(MockitoExtension.class)时,这就是谷歌带我去的地方。

Turns out I hadn't included the maven-surefire-plugin in my pom.xml and that meant the @ExtendWith wasn't actually doing anything!原来我没有在我的 pom.xml 中包含maven-surefire-plugin ,这意味着@ExtendWith实际上没有做任何事情!

<build>
  <plugins>
    <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-surefire-plugin</artifactId>
      <version>2.22.1</version>
    </plugin>
    ...

In my case a tested method called another method as a parameter:在我的例子中,一个测试方法调用另一个方法作为参数:

Mockito.`when`(repository.getItems(prefs.getUser().id)).thenReturn(listOf())`

While repository is mocked, prefs.getUser().id) will throw NPE.在模拟repository时, prefs.getUser().id)将抛出 NPE。 So, first we should mock a parameter, for instance,所以,首先我们应该模拟一个参数,例如,

Mockito.`when`(prefs.getUser()).thenReturn(User(id = 1, name = "user"))`

Also we should mock prefs .我们也应该模拟prefs I didn't check it and changed a library, sorry.我没有检查它并改变了一个图书馆,对不起。

I was trying to mock a "final" method, which apparently was the problem.我试图模拟一个“最终”方法,这显然是问题所在。

The right way to handle this would be to use an interface and mock that interface however I couldn't control the library where the "final" method was.处理此问题的正确方法是使用接口并模拟该接口,但是我无法控制“最终”方法所在的库。

Mockito 2 can handle mocking final method. Mockito 2 可以处理模拟 final 方法。 Add a text file to the project's src/test/resources/mockito-extensions directory named org.mockito.plugins.MockMaker and add a single line of text:在项目的 src/test/resources/mockito-extensions 目录中添加一个名为 org.mockito.plugins.MockMaker 的文本文件并添加一行文本:

mock-maker-inline

After that, mocking the final method should work just fine.之后,模拟最终方法应该可以正常工作。

Check which version of Junit you are using.检查您使用的 Junit 版本。 In the Maven/Gradle build tool, if you set to use testRuntimeOnly 'junit5' ,then it might not take @RunWith since it is not available and it is replaced with @ExtendWith in Junit5.在 Maven/Gradle 构建工具中,如果您设置使用testRuntimeOnly 'junit5' ,那么它可能不会使用@RunWith ,因为它不可用并且在 Junit5 中它被替换为@ExtendWith

This doesnt answer the OP's original query, but its here to try help others with Mockito null pointer exceptions (NPE).这不回答 OP 的原始查询,但在这里尝试帮助其他人解决 Mockito null 指针异常(NPE)。

My NPE was happening as I did not explicitly set the class under tests' dependencies to be the classes I had mocked.我的 NPE 正在发生,因为我没有将测试依赖项下的 class 明确设置为我模拟的类。 So the class under test was unable to find its required dependencies, creating the NPE.因此,被测 class 无法找到其所需的依赖项,从而创建了 NPE。 I tend to not mock the class under test (ie use new keyword), to ensure im getting my native class behaviour for testing.我倾向于不模拟正在测试的 class(即使用new关键字),以确保我得到我的原生 class 行为进行测试。

Im still using Junit 4 for reasons outside my control.由于我无法控制的原因,我仍在使用 Junit 4。 Worked example;工作示例;

ClassUnderTest待测类

public class ClassUnderTest {
    private DependantClassOne dependantClassOne; 
    private DependantClassTwo dependantClassTwo;

    // remaining class, including setters
}

Test Class测试 Class

@RunWith(MockitoJUnitRunner.class)
public class Test {
    private ClassUnderTest classUnderTest;
    private DependantClassOne dependantClassOne;
    private DependantClassTwo dependantClassTwo;
 
    @Before
    public void setup() {
        dependantClassOne = mock(DependantClassOne.class);
        dependantClassTwo = mock(DependantClassTwo.class);
        classUnderTest = new ClassUnderTest();

        classUnderTest.setDependantClassOne(dependantClassOne); //added to prevent NPE
        classUnderTest.setDependantClassTwo(dependantClassTwo); //added to prevent NPE
    }

    // tests
}

In my case, my Mockito annotation didn't match the JUnit Version.就我而言,我的 Mockito 注释与 JUnit 版本不匹配。

  1. When using @ExtendWith(MockitoExtension.class) make sure you're using JUnit 5: import org.junit.jupiter.api.Test;使用@ExtendWith(MockitoExtension.class)时,请确保您使用的是 JUnit 5: import org.junit.jupiter.api.Test;

  2. When using @RunWith(MockitoJUnitRunner.class) make sure you're using JUnit 4: import org.junit.Test;使用@RunWith(MockitoJUnitRunner.class)时,请确保您使用的是 JUnit 4: import org.junit.Test;

In my case UnderTest class has two kind of injections: via constructor(TheSecondService) and via setters(TheFirstService).在我的案例中,UnderTest class 有两种注入:通过构造函数 (TheSecondService) 和通过设置器 (TheFirstService)。

All injections via setters was null. This helps:通过 setter 进行的所有注入都是 null。这有助于:

@Mock
private TheFirstService theFirstService;
@Mock
private TheSecondService theSecondService;
@InjectMocks
private TheThirdService underTest;
  
@BeforeEach
public void setup() {
    underTest.setTheFirstService(theFirstService);
}

faced the same issue, the solution that worked for me:面临同样的问题,对我有用的解决方案:

Instead of mocking the service interface, I used @InjectMocks to mock the service implementation:我没有模拟服务接口,而是使用 @InjectMocks 来模拟服务实现:

@InjectMocks
private exampleServiceImpl exampleServiceMock;

instead of :代替 :

@Mock
private exampleService exampleServiceMock;

就我而言,我在映射器声明中缺少注释@mock

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM