Final Kotlin class can not be mocked because method "should return Validator"

Viewed 716

I am trying to write unit tests for my Javalin.io web application. There are a few references to Mockito being used for mocking the Context objects, which is Javalins way to give the user access to the incoming web requests. I am trying to mock the .header(String) method of the Context class because the unit under test is reading the "Authorization" header and performs a JWT check on it.

My pom contains a recent version of Mockito, which should be able to mock final classes:

<dependency>
  <groupId>org.mockito</groupId>
  <artifactId>mockito-core</artifactId>
  <version>3.2.0</version>
  <scope>test</scope>
</dependency>

I have enabled the inline mock maker as described in the Mockito documentation by creating the file resources/mockito-extensions/org.mockito.plugins.MockMaker with the content mock-maker-inline.

Now I have a stupid test which mocks a Context object and should return "hello123" whenever the header() method of the context object is called. The following code is part of a real unit test but is enough to cause an exception when running the test:

  @Test
  void stupidTest1() {
    Context context = mock(Context.class);

    String test1 = "hello123";
    when(context.header("Authorization")).thenReturn(test1);
  }

and also tried this:

  @Test
  void stupidTest1() {
    Context context = mock(Context.class);

    String test1 = "hello123";
    given(context.header("Authorization")).willReturn(test1);
  }

Executing this test with mvn test fails with the exception:

org.mockito.exceptions.misusing.WrongTypeOfReturnValue: 

String cannot be returned by header()
header() should return Validator
***
If you're unsure why you're getting above error read on.
Due to the nature of the syntax above problem might occur because:
1. This exception *might* occur in wrongly written multi-threaded tests.
   Please refer to Mockito FAQ on limitations of concurrency testing.
2. A spy is stubbed using when(spy.foo()).then() syntax. It is safer to stub spies - 
   - with doReturn|Throw() family of methods. More in javadocs for Mockito.spy() method.

    at my.package.stupidTest1(JavalinTest.java:28)

Is there something I am doing wrong? One point that is weird is that the test sometimes runs successfully, but fails most of the time, especially when running the mvn test command a few times in a row.

3 Answers

I could reproduce the issue at my local. It seems there is an issue during classloading and Mockito cannot find the correct method every time. You can change the test as below to make sure it finds the correct header method. It worked at my local.

@Test
public void stupidTest1() {
    Context context = mock(Context.class);
    String test1 = "hello123";
    Validator<String> f = new Validator<String>("hello123","Value");
    when(context.header(test1,String.class)).thenReturn(f);
}
open class Context{

    fun header(name: String) : String {
        var token = "DEFAULT TOKEN"
        if(name == "AUTHORIZATION"){
            token =  "Bearer 01234"
        }
        return token
    }

}

Test Class:

open class ContextTest {

    var context: Context = mock()

    @Test
    fun testContextWhenHeaderIsAuth(){
        /* Return what the real method returns.*/
        whenever(context.header("AUTHORIZATION")).thenCallRealMethod()
        assertEquals("Bearer 01234", context.header("AUTHORIZATION"))

        /* Mocking the return value to be something you need*/
        whenever(context.header("AUTHORIZATION")).thenReturn("Bearer XXX")
        assertEquals("Bearer XXX", context.header("AUTHORIZATION"))
    }

}

I tried several of the suggestions here and could not consistently get Mockito to work with io.javalin.http.Context. It would work for a time and then stop working. Or it would work in the debugger but not when executed normally.

A Suggestion by @Mobigital worked for me.

Mock the HttpServletRequest and HttpServletResponse.

@Test
public void get_csv() throws SQLException
{
    final HttpServletRequest request= mock(HttpServletRequest.class);
    final HttpServletResponse response = mock(HttpServletResponse.class);
    final Map<Class<?>, ?> map = new LinkedHashMap<>();

    // Mocking the Context was not working correctly.
    // https://stackoverflow.com/questions/61792392/final-kotlin-class-can-not-be-mocked-because-method-should-return-validator
    Context ctx = new Context(request, response, map);
    
    when(request.getAttribute("database")).thenReturn(getConnection());

    when(request.getHeader(Header.ACCEPT)).thenReturn("text/csv"); 
    when(request.getQueryString()).thenReturn("my_param=some_value");

    LocationGroupController controller = new LocationGroupController(new MetricRegistry());
    controller.getAll(ctx);

    verify(response).setStatus(200);
    verify(response).setContentType("text/csv");
}
Related