Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unit Testing verifying a companion object method is called (mocking a companion object)

When switching to Kotlin, static methods are moved into a companion object. However, there is no obvious way to unit test other methods which call these "static method".

In Java, we could use PowerMockito's MockStatic(SomeClass.class) to verify a static method is called in the method under test. PowerMock loses its magic in Kotlin.

For testing, I have create the following classes.

public class DummyJava {
    static public Void staticMechod(){
          System.out.print("Dummy method is called");
          return null;
     }
}

class DummyCompanion {
    companion object {
        fun someCompanionMethod(){
            System.out.printf("companion method is called\n")
        }
    }
}

Now there is a another class which calls the DummyCompanion.someCompanion

public class DummyWrapper {
    public void callAStaticMethod(){
        DummyJava.staticMechod();
    }

    public void callCompanionMethod(){
        DummyCompanion.Companion.someCompanionMethod();
    }
}

To unit test callAStaticMethod() We used the following

@RunWith(PowerMockRunner.class)
@PrepareForTest({DummyJava.class, DummyCompanion.Companion.class})
public class staticClassTest {
    //This case works
    @Test 
    public void testForStaticMethod() {
        PowerMockito.mockStatic(DummyJava.class);   
        DummyWrapper testObject = new DummyWrapper();

        Mockito.when(DummyJava.staticMechod()).thenCallRealMethod();

        testObject.callAStaticMethod();

        PowerMockito.verifyStatic(Dummy.class);
        DummyJava.staticMechod();
    }

    //This case doesn't work. It always passes.
    @Test
    public void testForCompanionMethod() {
        PowerMockito.mockStatic(DummyCompanion.Companion.class);
        DummyWrapper testObject = new DummyWrapper();
        testObject.callCompanionMethod();
PowerMockito.verifyStatic(DummyCompanion.Companion.class,Mockito.times(1));
        DummyCompanion.Companion.someCompanionMethod();
}

My question is how to verify the companion method is called.

like image 774
HarryQ Avatar asked Nov 06 '18 15:11

HarryQ


People also ask

How do you mock an object in unit testing?

An object under test may have dependencies on other (complex) objects. To isolate the behavior of the object you want to replace the other objects by mocks that simulate the behavior of the real objects. This is useful if the real objects are impractical to incorporate into the unit test.

What is mocking a method?

Mocking is done when you invoke methods of a class that has external communication like database calls or rest calls. Through mocking you can explicitly define the return value of methods without actually executing the steps of the method.

What is mocking in unit testing Java?

Mocking is a technique of unit testing a class, where we mock an external dependency in order to test our classes and methods. When unit tests are written well with mocks, they would not have any external dependencies and will not fail when external stuff changes. We looked at the Mockito framework.

What is companion object used for?

companion object is how you define static variables/methods in Kotlin. You are not supposed to create a new instance of Retrofit / ApiService each time you execute a request, however.


3 Answers

Solution 1 : add a caller function in the calling class

public class DummyWrapper {
val foo get() = DummyCompanion.Companion

public void callAStaticMethod(){
    foo.staticMechod();
}

public void callCompanionMethod(){
    foo.someCompanionMethod();
}
}

In the test class, we can use Mockito to provide a stub for the get() function and verified it is called.

@Test
fun testCase{
....
val mockCompanionObj: DummyCompanion.Companion = mock()
val wrapper = DummyWrapper()

whenever(wrapper.foo).thenReturn(mockCompanionObj)
wrapper.callCompanionMethod()
verify(mockCompanionObj).someCompanionMethod()
....
}

Solution 2: using Mockk Mocking companion object in Mockk is easy. There is no need to insert a test interfacing object in the source code.

 @Test
 fun testCompanionObject() {
    //Mock the companion object
    mockkObject(DummyCompanion.Companion)

    //define the stubbing bechavior of a companion object method
    every { DummyCompanion.Companion.companionMethod() } answers { stubMethod() }

    val testObject = DummyWrapper()

    //Call a method that calls the companion object method
    //You can verify stubMethod() is called
    testObject.callCompanionMethod()

    verify(exactly = 1) { DummyCompanion.someCompanionMethod() }
}

For details see Mockk

like image 103
HarryQ Avatar answered Sep 18 '22 09:09

HarryQ


You can do so with PowerMock too, it'd be like this:

@RunWith(PowerMockRunner.class)
@PrepareForTest({DummyCompanion.class})
public class staticClassTest {

    @Test
    public void testForCompanionMethod() {
       PowerMockito.mockStatic(DummyCompanion.class);
       DummyCompanion.Companion companionMock = PowerMockito.mock(DummyCompanion.Companion.class);
       Whitebox.setInternalState(
        DummyCompanion.class, "Companion",
        companionMock
       );

       DummyWrapper testObject = new DummyWrapper();
       testObject.callCompanionMethod();
       Mockito.verify(companionMock,Mockito.times(1)).someCompanionMethod();
    }
}

Kotlin creates for Java (in the Kotlin class, which is DummyCompanion in this case) a static field of its Companion subclass named Companion which can be set using PowerMock's WhiteBox.setInternalState tool to a mocked Companion instance that you can later verify method calling to.

like image 41
César Muñoz Avatar answered Sep 20 '22 09:09

César Muñoz


Here is one more solution that doesn't require Mockk or PowerMock.

  1. Extract an interface from the companion object and place it in the same file but outside of the class.
  2. Have the companion object inherit from the interface
  3. Add a new optional property to the constructor of the class calling the static methods. Its default value should be the companion object.
  4. Switch to using instance calls (lowercase helper) in the calling class. Now it can be mocked normally in unit tests.
interface IStaticHelper {
    fun foo(): String
    fun bar(): String
}

class StaticHelper {
    companion object : IStaticHelper {
        override fun foo() = "foo"
        override fun bar() = "bar"
    }
}

class Caller(private val helper: IStaticHelper = StaticHelper.Companion) {
    fun callsTheHelper(): String {
        return helper.foo()
    }
}

class CallerTest {
    @Test
    fun testCallsTheHelper() {
        val helper = mock()
        val caller = Caller(helper)
        assertThat(caller.callsTheHelper()).isEqualTo("foo")
    }
}

While it's true that the caller in this case is no longer making static method calls, other classes can continue to do so unchanged.

like image 39
Takaitra Avatar answered Sep 19 '22 09:09

Takaitra