Skip to content

Mockito won't stub method as expected if the method implemented with Singleton pattern. #461

Description

@CharlesZ-Chen

I have a java class (but I think java shouldn't matter, the issue should persist even given a scala implementation)

abstract class MyClass {
   String fieldToCacheSerialization = null;
   
   public String serialize() {
      if(fieldToCacheSerialization == null) {
           fieldToCacheSerialization = expensiveSerialization();
      }
      return fieldToCacheSerialization;
   }
  
   /** delegate to sub class implementation, but it is expensive operation
   * and therefore we want to cache it.
   * /
   public abstract String expensiveSerialization() {...}
}

I want to stub MyClass.serialize() call in my scala test suite via Mockito:

test("some test should work as expected") {
   val myClassMock = mock[MyClass]
   when(myClassMock.serialize()).thenReturn("stubbedSerialization") // <--- This will not work as expected.
}

I found the stubbing will not work, as Mockito will register a method stub for MyClass.expensiveSerialization() instead of MyClass.serialize() -- however, during the actual method invocation when Mockito registering the stub, fieldToCacheSerialization will initialize with an empty String (I guess that is the smartNull response for a non-stub yet expensiveSerialization() invocation?). This will lead the stub register on MyClass.expensiveSerialization() NEVER served stub call in test!

I currently have to workaround with:

test("some test should work as expected") {
   val myClassMock = mock[MyClass]
   when(myClassMock.expensiveSerialization()).thenReturn("stubbedSerialization") // <-- Add this line fix the issue
   when(myClassMock.serialize()).thenReturn("stubbedSerialization")

However, I kind of dislike the workaround as:

  1. It leaks MyClass implementation details to my test suite -- in my test suite, MyClass.serialize() is the contact point for my test scope - I do not want to set up the stub only with having to know how MyClass.serialize() is implemented
  2. It is tricky that stubbing only on myClassMock.serialize() won't work -- took me a lot of time to realize is Mockito didn't stub as expected (thought my implementation was wrong and mislead me to debug my implementations).

Wondering is there a way to tell Mockito just directly stub the method invocation that user specified instead of trying to "infer" a leaf method invocation to stub with? (e.g. do not register stub on MyClass.expensiveSerialization() when user is actually specify the stub on MyClass.serialize()).

Thanks a lot!

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions