Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Constructor Injected Class Instance Variable Loses Mocking doThrow or doReturn Doesn't work #91

Closed
gursahibsahni opened this issue Apr 3, 2019 · 8 comments

Comments

@gursahibsahni
Copy link
Contributor

I have a spring boot application and am using MockitoSugar to write tests.
Spring boot version: 2.0.5.RELEASE
Scala version: 2.11.8
mockito-scala_2.11: 1.2.4

Injecting mock in constructor:

 val headerParamsResolverMock = mock[HeaderParamsResolver]
 val irsArgumentResolver = new IRSArgumentResolver(headerParamsResolverMock)

Test Case:

"resolverArgument" should "invoke resolve method if header parameter is not resolved" in {
val parameter = mock[Parameter]
doReturn(parameter, Nil: _*).when(methodParameterMock).getParameter
doReturn("string", Nil: _*).when(parameter).getName
doReturn(false, Nil: _*).when(headerParamsResolverMock).isResolved
doReturn("string", Nil: _*).when(headerParamsResolverMock).resolve(mock[NativeWebRequest]) // doesn't work


irsArgumentResolver.resolveArgument(methodParameterMock, mock[ModelAndViewContainer], mock[NativeWebRequest],
    mock[WebDataBinderFactory])

Mockito.verify(headerParamsResolverMock).resolve(mock[NativeWebRequest])
}

Class under test

 @Component
 public final class IRSArgumentResolver implements HandlerMethodArgumentResolver {

private HeaderParamsResolver headerParamsResolver;

public IRSArgumentResolver(HeaderParamsResolver headerParamsResolver) {
    this.headerParamsResolver = headerParamsResolver;
 }
@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest,
                              WebDataBinderFactory binderFactory) {

    String sample = "";
    if (!headerParamsResolver.isResolved())
        sample = headerParamsResolver.resolve(webRequest);

    System.out.println(sample); // here its null 
    return webRequest.getParameter(parameter.getParameter().getName());
}

}

If I see the debugger, the headerParamsResolver isn't shown as a mock object like other method parameters show up. Like Mock for Method Paramater OR Mock for ModelAndViewContainer etc. Moreover, only the isResolved class instance variable is set but other mock features like doThrow or doReturn doesn't work. PFA debugger screen.

Screen Shot 2019-04-03 at 1 57 10 PM

Moreover, this test case throws exception:
Screen Shot 2019-04-03 at 2 28 23 PM

@gursahibsahni gursahibsahni changed the title Constructor Injected Class Instance Variable Lost Mock And Verify Doesn't Work Constructor Injected Class Instance Variable Loses Mocking doThrow or doReturn Doesn't work Apr 3, 2019
@ultrasecreth
Copy link
Member

@gursahibsahni Hi there, 2 things

  1. Could you create a tiny GitHub project so I can clone and reproduce the issue?
  2. Looking at your StackTrace and code it seems to me you are not using anything from mockito-scala (you are using MockitoSugar from Scalatest and the Mockito object from the core Java lib), would you mind switching to it and try? your issue may be one of the many already solved in the lib.

Thanks

@gursahibsahni
Copy link
Contributor Author

@bbonanno sorry about the confusion.

I have replaced MockitoSugar of Scalatest to Mockito lib. Also, I have created a GIT repo here: https://github.com/gursahibsahni/debug-mockito-scala

The exception has changed and its a missing dependency

*** RUN ABORTED *** java.lang.NoClassDefFoundError: org/mockito/internal/invocation/InterceptedInvocation at org.mockito.internal.handler.ScalaMockHandler.handle(ScalaMockHandler.scala:23) at org.mockito.internal.handler.ScalaNullResultGuardian.handle(ScalaNullResultGuardian.scala:10) at org.mockito.internal.handler.InvocationNotifierHandler.handle(InvocationNotifierHandler.java:35) at org.mockito.internal.creation.bytebuddy.MockMethodInterceptor.doIntercept(MockMethodInterceptor.java:65) at org.mockito.internal.creation.bytebuddy.MockMethodAdvice.handle(MockMethodAdvice.java:107)

@ultrasecreth
Copy link
Member

@gursahibsahni There are quite a few things wrong with this, let's separate them in 2

  1. The project you uploaded to GitHub fails because maven resolves the wrong artifacts all around, similar to this bug, I had to manually add mockito-core and bytebuddy to make it run.
        <dependency>
            <groupId>org.mockito</groupId>
            <artifactId>mockito-core</artifactId>
            <version>2.25.1</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>net.bytebuddy</groupId>
            <artifactId>byte-buddy</artifactId>
            <version>1.9.7</version>
        </dependency>
        <dependency>
            <groupId>net.bytebuddy</groupId>
            <artifactId>byte-buddy-agent</artifactId>
            <version>1.9.7</version>
        </dependency>
  1. The test you wrote has some issues
  • The cause of the exception you posted is that you can't create a mock while you are verifying (line 57), I guess you probably want something like
Mockito.verify(headerParamsResolverMock).resolve(any[NativeWebRequest])
  • HeaderParamsResolver is being mocked properly, the fact that it looks like the real thing in the debugger is due to how the inline mocker works
  • Line 50 should stub false (I guess this is a typo as in the code above it does that)
  • The last stubbing is never going to work as nothing is gonna be equal to a mock you create and dispose, again I guess you want
doReturn("string", Nil: _*).when(headerParamsResolverMock).resolve(any[NativeWebRequest])
  • You still use pretty much the Java Mockito everywhere, if you were to use mockito-scala, your test would look more like
  "resolverArgument" should {
    "invoke resolve method if header parameter is not resolved" in {
      val parameter = mock[Parameter]

      doReturn(parameter).when(methodParameterMock).getParameter
      doReturn("string").when(parameter).getName
      doReturn(false).when(headerParamsResolverMock).isResolved
      doReturn("string").when(headerParamsResolverMock).resolve(*)
      
      irsArgumentResolver.resolveArgument(methodParameterMock, mock[ModelAndViewContainer], mock[NativeWebRequest], mock[WebDataBinderFactory])

      verify(headerParamsResolverMock).resolve(*)
    }
  }

@gursahibsahni
Copy link
Contributor Author

gursahibsahni commented Apr 4, 2019

@bbonanno big thanks for addressing all the points. Especially the incorrect maven resolver.

All looks good and it feels so good writing verbose but less code for unit tests.
One thing I would bring up is that the shouldBe matcher is not implicit from the docs that its inherited from org.scalatest.Matchers. If you would like to have it on the README.md section of it, I can update it. Let me know your thoughts 👍

@ultrasecreth
Copy link
Member

@gursahibsahni No problem

You mean the examples in the tests that use shouldBe?
If that I thought it was pretty obvious as most of the examples are written in Scalatest, but yeah, it would be nicer to actually use something agnostic of the test framework (as I'm soon adding support for Specs2), if you fancy raise a pull request on that

@gursahibsahni
Copy link
Contributor Author

@bbonanno I tried to raise a PR against release/1.x but I was denied permission to push. I couldn't find the contribution guidelines I can refer to.

@ultrasecreth
Copy link
Member

@gursahibsahni Is the standard flow, you have to fork the repo, push into that fork and raise a pull request from there to this repo.

@gursahibsahni
Copy link
Contributor Author

thanks for the instructions

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants