25
votes

I am unable to mock a Kotlin final class using Mockito 2. I am using Robolectric in addition.

This is my test code:

@RunWith(RobolectricTestRunner.class)
@Config(constants = BuildConfig.class, sdk = 21)
public class Test {

    // more mocks

    @Mock
    MyKotlinLoader kotlinLoader;

    @Before
    public void setUp() {
        MockitoAnnotations.initMocks(this);
    }
}

The test fails when we try to initialise the mocks in the setUp() method.

In addition, I am using the following gradle dependencies in my code:

testCompile 'org.robolectric:robolectric:3.3.2'
testCompile 'org.robolectric:shadows-multidex:3.3.2'
testCompile 'org.robolectric:shadows-support-v4:3.3.2'
testCompile("org.powermock:powermock-api-mockito2:1.7.0") {
    exclude module: 'hamcrest-core'
    exclude module: 'objenesis'
}
testCompile 'junit:junit:4.12'
testCompile 'org.mockito:mockito-inline:2.8.9'

All other unit tests pass using this configuration but as soon as I try to mock the Kotlin class it throws the following error:

Mockito cannot mock/spy because : - final class

Please note I am using Mockito version 2 and I am using the inline dependency which automatically enables the ability to mock final classes.

8
Did you follow the documentation and create the file /mockito-extensions/org.mockito.plugins.MockMaker containing the value mock-maker-inline? - Seelenvirtuose
You don't need to do that with this dependancy: testCompile 'org.mockito:mockito-inline:2.8.9' - blackpanther
Where it's stated that we don't have to use /mockito-extensions/org.mockito.plugins.MockMaker with mockito 2.8.9? Once I removed that file I started got the same error, so looks like it's still necessary to use that file even with mockito 2.8.9. - Eugene Brusov
For me it works as @blackpanther suggests (using 2.+ instead of 2.8.9). @EugeneBrusov: It's written: here (in the meantime) ... As a convenience, the Mockito team provides an artifact where this mock maker is preconfigured. Instead of using the mockito-core artifact, include the mockito-inline artifact in your project. Note that this artifact is likely to be discontinued once mocking of final classes and methods gets integrated into the default mock maker. - yasd
The mock-maker-inline works but it's really slow I recommend you to use the all-open Compiler Plugin from Kotlin - Brais Gabin

8 Answers

1
votes

You may use Powermock for this, for example:

import static org.powermock.api.mockito.PowerMockito.mock;
import static org.powermock.api.mockito.PowerMockito.spy;
import static org.powermock.api.mockito.PowerMockito.when;

@RunWith(RobolectricTestRunner.class)
@Config(constants = BuildConfig.class, sdk = 21)
@PowerMockIgnore({ "org.mockito.*", "org.robolectric.*", "android.*" })
@PrepareForTest({FinalClass1.class, FinalClass2.class})
public class Test {
    @Rule
    public PowerMockRule rule = new PowerMockRule();

    ... // your code here
}
14
votes

PowerMock implements its own MockMaker which leads to incompatibility with Mockito mock-maker-inline, even if PowerMock is just added as a dependency and not used. If two org.mockito.plugins.MockMaker exist in path then any only one can be used, which one is undetermined.

PowerMock can however delegate calls to another MockMaker, and for then tests are run without PowerMock. Since PowerMock 1.7.0 this can be configured with using the PowerMock Configuration.

The MockMaker can be configured by creating the file org/powermock/extensions/configuration.properties and setting:

mockito.mock-maker-class=mock-maker-inline

Example of using Mockito mock-maker-inline with PowerMock: https://github.com/powermock/powermock-examples-maven/tree/master/mockito2

9
votes

Since Mockito 2.1.0 there is a possibility to mock final types, enums, and final methods. It was already mentioned in the comments to the original question.

To do this, you’ll need to create a folder (if dont exist) test/resources/mockito-extensions and add there file with the name org.mockito.plugins.MockMaker and this line:

mock-maker-inline

enter image description here

Links to documentation and tutorial

2
votes

Try adding this below dependency to your build.gradle.

testImplementation 'org.mockito:mockito-inline:2.8.47'

Replace with your mockito version instead of 2.8.47. This will help you to avoid using powermock for the issue.

please look into the below link to know how this thing works.

How to mock a final class with mockito

1
votes

mock-maker-inline works as is pointed out in other answers. But It's really slow. You can use the all-open plugin to avoid this problem.

To do so you need:

  1. Create an Annotation:
annotation class Mockable
  1. Activate all-open in your build.gradle file:
dependencies {
  classpath "org.jetbrains.kotlin:kotlin-allopen:$kotlin_version"
}

apply plugin: 'kotlin-allopen'

allOpen {
  annotation('com.example.Mockable')
}
  1. Annotate the classes that you want to mock:
@Mockable
class Foo {
  fun calculateTheFoo(): Int {
    sleep(1_000) // Difficult things here
    return 1
  }
}

If you want more information you can read my blog post where I explain this with more details: Mocking Kotlin classes with Mockito — the fast way

0
votes

Let us program to interfaces, not implementations. You can extract an interface, use it in your code, and mock it. For example, the following will not work:

import com.nhaarman.mockito_kotlin.mock
class MyFinalClass {...}
(snip)
private val MyFinalClass = mock()

So let us extract an interface:

class MyFinalClass : MyInterface {...}
(snip)
private val MyInterface = mock()
-1
votes

Because in kotlin all classes are final by default.

You should also consider adding open to the class declaration.

Example: open class MyClasss{}

-1
votes

Kotlin makes the decorator pattern dead simple and concise to implement:

open class OpenClass() : SomeInterface by FinalClass()

This will, in effect, override each of the SomeInterface members with wrapped calls to the FinalClass() instance in your open wrapper class. You can then inject this wrapper into your tests.

I had to resort to this on a project where the target class lived in a library, and seemed to be untouchable by the all-open compiler plugin.