Skip to content

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

@CharlesZ-Chen

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

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions