4

I am having a simple controller class

@RestController
open class MyController() {

@Autowired
lateinit var myInterface: MyInterface

@GetMapping(value = ["/v1/call-Api"], produces = ["application/json"])
fun getData():Response{
   callFx()
   /// Here I have logic
 }

  fun callFx():String{
    return myInterface.getmyStringData()
  }
}

Now Come to implementation part of

MyInterface

@Service
class MyImpl: MyInterface {
  override fun getmyStringData(){
        return "Some string"
    }
}

Please note that for MyInterface, I have only one implementation class.

Now come to Test case of controller class

class ControllerTest{

@Autowired
lateinit var myIntF: Myinterface


@Test
fun controllerTest(){         
Mockito.`when`(myIntF.getmyStringData()).thenReturn("Some mock string")
// Some code over here
}

}

After all these I am keep getting below error

  org.mockito.exceptions.misusing.MissingMethodInvocationException: 
  when() requires an argument which has to be 'a method call on a mock'.
  For example:
  when(mock.getArticles()).thenReturn(articles);

  Also, this error might show up because:
  1. you stub either of: final/private/equals()/hashCode() methods.
  Those methods *cannot* be stubbed/verified.
  Mocking methods declared on non-public parent classes is not supported.
  2. inside when() you don't call method on mock but on some other object.

Even though code syntax belongs to Kotlin but i keep it simple to elaborate me scenario. Any JAVA guy can also help me.

Any help would be really helpful for me.

john
  • 359
  • 1
  • 4
  • 15
  • Are you using another `@Profile` for your tests? How do you define `Myinterface` as a mock? – tynn Apr 04 '20 at 08:45

2 Answers2

3

The problem:

Below is your test class

class ControllerTest{

@Autowired
lateinit var myIntF: MyInterface


@Test
fun controllerTest(){         
Mockito.`when`(myIntF.getmyStringData()).thenReturn("Some mock string")
// Some code over here
}

Since you have used @Autowired, the real implementation is used and not the mock object and hence when you do Mockito.when(myIntF.getmyStringData()).thenReturn("Some mock string") , you get the error when() requires an argument which has to be 'a method call on a mock'. This is because myIntF is not a mock object.

The solution:

First, since it's a controller test, you need to have the controller field annotated with @InjectMocks to inject the mocked MyInterface obj into it. Then you need to annotate the MyInterface field with @Mock which would create a mocked object. Then you need to have a @Before or @BeforeEach method with MockitoAnnotations.initMocks(this) to initialize objects annotated with Mockito annotations. Only after that, the method call mocking with Mockito.when(mockedObject.methodCall).thenReturn(mockedValue) would work.

class ControllerTest{

@InjectMocks
lateinit var controller: MyController

@Mock
lateinit var myIntF: MyInterface

@BeforeEach
fun init() {
    MockitoAnnotations.initMocks(this)
}


@Test
fun controllerTest(){         
    Mockito.`when`(myIntF.getmyStringData()).thenReturn("Some mock string")
    // Some code over here
    controller.callFx() //this would return "Some mock string"
}
Madhu Bhat
  • 13,559
  • 2
  • 38
  • 54
1

I'm not familiar with @Autowire so this might be a completely wrong assumption, but it's also too big for a comment, so here it goes.

The exception basically explains that the object you're trying to mock isn't a mock and from what I can see, this is true.

Usually one can do something like:

@Mock
lateinit var myIntF: Myinterface

@Before
fun setUp() {
  MockitoAnnotations.initMocks(this)
}

And now the mock is created and you can configure it with when like you have.

There are other options to initialize this, such as running the test with the mockito test runner. I believe there's also a test rule and you'll always have mockito-kotlin which is great for kotlin code and it's much simpler in my personal opinion:

lateinit var myIntF = mock<Myinterface>()

@Test
fun controllerTest(){ 
  myIntF.stub {
   on { getmyStringData() } doReturn "Some mock string"
  }
}

The point here is that I think you didn't actually create a mock and mockito needs this because if I'm not mistaken it works by inheriting from the class it's mocking.

Edit:

As pointed out in the comments you might want to test the controller. This means you'll need to instantiate it with the mocks you've created. One suggested way is to use @InjectMocks. Something like:

@InjectMocks
lateinit var controller: MyController

But without knowing the whole test code it's hard to say this is exactly what you want.

Fred
  • 16,367
  • 6
  • 50
  • 65
  • @Fred..Thanks for response but it's calling actual Implementation. Ideally when mocking or stubbing has been done then actual implementation shouldn't be get call..But thanks agian.. – john Apr 04 '20 at 09:09
  • That's definitely not possible. First it's an interface, so it can't be calling the actual implementation and second, `mock` does not call the real implementation ever. You'd have to use a `spy` and actually create the instance yourself. Please check that there are no another things interfering, such as the `Autowired` annotation. – Fred Apr 04 '20 at 12:30
  • @jhon actually reading what auto wired does, it sounds like if you keep it in the test it'll inject your mock with the defined implementation. I don't think you want that, so I guess you want to remove that annotation? – Fred Apr 04 '20 at 12:33
  • The answer seems correct, but it's also necessary to add the annotation `@InjectMocks` to the controller in the test, see https://stackoverflow.com/q/16467685/6245535 – Loris Securo Apr 04 '20 at 12:36
  • @Loris Securo that might be true. I just didn't see any instance of the controller in the code so wasn't sure where the mock was going. I can update the answer if I know exactly how the mock should be used – Fred Apr 04 '20 at 12:38