dpreussler / kotlin-testrunner

JUnit4Testrunner that removes final from classes and methods, especially needed in kotlin projects
113 stars 8 forks source link

[bug] Fails to mock same class in multiple Spec files (spock/kotlin/android) #25

Open OliverCulleyDeLange opened 6 years ago

OliverCulleyDeLange commented 6 years ago

I'm using kotlin-testrunner to run Spock tests against a Kotlin Android project.

I'm seeing an error when i try to mock the same class in more than one Spec file. For example if i mock an Android View View dummyView = Mock(View) in two Spec files, i see the following error when the second spec file runs (The first spec file runs and passes):

org.spockframework.mock.CannotCreateMockException: Cannot create mock for class android.view.Viewnull

    at org.spockframework.mock.runtime.MockInstantiator.instantiate(MockInstantiator.java:38)
    at org.spockframework.mock.runtime.ProxyBasedMockFactory$CglibMockFactory.createMock(ProxyBasedMockFactory.java:92)
    at org.spockframework.mock.runtime.ProxyBasedMockFactory.create(ProxyBasedMockFactory.java:49)
    at org.spockframework.mock.runtime.JavaMockFactory.createInternal(JavaMockFactory.java:59)
    at org.spockframework.mock.runtime.JavaMockFactory.create(JavaMockFactory.java:40)
    at org.spockframework.mock.runtime.CompositeMockFactory.create(CompositeMockFactory.java:44)
    at org.spockframework.lang.SpecInternals.createMock(SpecInternals.java:45)
    at org.spockframework.lang.SpecInternals.createMockImpl(SpecInternals.java:281)
    at org.spockframework.lang.SpecInternals.MockImpl(SpecInternals.java:99)
    at uk.co.oliverdelange.android_kotlin_spock_livedata.MainViewModelSpec.$spock_initializeFields(MainViewModelSpec.groovy:18)
Caused by: java.lang.ClassCastException: Cannot cast android.view.View$$EnhancerByCGLIB$$4a4c03cf to android.view.View$$EnhancerByCGLIB$$4a4c03cf
    at java.lang.Class.cast(Class.java:3369)
    at org.spockframework.mock.runtime.MockInstantiator$ObjenesisInstantiator.instantiate(MockInstantiator.java:45)
    at org.spockframework.mock.runtime.MockInstantiator.instantiate(MockInstantiator.java:31)
    ... 9 more

Initially I thought it was an issue with Spock, however when I don't use the SpotlinTestRunner, the issue goes away.

The issue isn't limited to Android classes, it happens when i try to mock my own Kotlin classes too.

You can see the error in my sample codebase, look at the MainViewModelSpec and run the UNIT Test run configuration and you should see the issue.

Thanks in advance!

npresseault commented 6 years ago

Hi Olivier, I've had the same issue so I ended up creating this trait which can be imported in your specs. It is related to how objenesis is used to instanciate spock mocks. The workaround will clear the mock cache between each tests.

trait SpockObjenesisCacheTrait {

    @Before
    def clearClassCache() {
        try {
            final objStdField = ObjenesisHelper.getDeclaredField("OBJENESIS_STD")
            objStdField.accessible = true
            final objStd = objStdField.get(ObjenesisHelper)
            final cacheField = ObjenesisBase.getDeclaredField("cache")
            cacheField.accessible = true
            cacheField.set(objStd, null)
        } catch (Throwable e) {
            // ignore, failed to patch
        }
    }
}
RunninglVlan commented 6 years ago

@npresseault, as this is a hack anyway, I replaced a lot of text with one line, my trait became:

trait ObjenesisCacheClearer {

  @Before
  def before() {
    ObjenesisHelper.OBJENESIS_STD.cache = null
  }

  @After
  def after() {
    ObjenesisHelper.OBJENESIS_STD.cache = new ConcurrentHashMap<String, ObjectInstantiator<?>> ()
  }
}