-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(robolectric-extension): Add robolectric extension (#13)
- Loading branch information
Showing
3 changed files
with
264 additions
and
2 deletions.
There are no files selected for viewing
152 changes: 150 additions & 2 deletions
152
...ic-extension/src/main/kotlin/tech/apter/junit/jupiter/robolectric/RobolectricExtension.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,153 @@ | ||
package tech.apter.junit.jupiter.robolectric | ||
|
||
import org.junit.jupiter.api.extension.Extension | ||
import java.lang.reflect.Method | ||
import java.lang.reflect.Modifier | ||
import java.util.concurrent.atomic.AtomicBoolean | ||
import org.junit.jupiter.api.BeforeAll | ||
import org.junit.jupiter.api.extension.AfterAllCallback | ||
import org.junit.jupiter.api.extension.AfterEachCallback | ||
import org.junit.jupiter.api.extension.BeforeAllCallback | ||
import org.junit.jupiter.api.extension.BeforeEachCallback | ||
import org.junit.jupiter.api.extension.DynamicTestInvocationContext | ||
import org.junit.jupiter.api.extension.ExtensionContext | ||
import org.junit.jupiter.api.extension.InvocationInterceptor | ||
import org.junit.jupiter.api.extension.ReflectiveInvocationContext | ||
import org.junit.platform.commons.util.ReflectionUtils | ||
import tech.apter.junit.jupiter.robolectric.internal.JUnit5RobolectricTestRunnerHelper | ||
import tech.apter.junit.jupiter.robolectric.internal.createLogger | ||
import tech.apter.junit.jupiter.robolectric.internal.runOnMainThread | ||
import tech.apter.junit.jupiter.robolectric.internal.runOnMainThreadWithRobolectric | ||
import tech.apter.junit.jupiter.robolectric.internal.runWithRobolectric | ||
|
||
class RobolectricExtension : Extension | ||
class RobolectricExtension : | ||
InvocationInterceptor, | ||
BeforeAllCallback, | ||
BeforeEachCallback, | ||
AfterEachCallback, | ||
AfterAllCallback { | ||
private inline val logger get() = createLogger() | ||
private val beforeAllFired = AtomicBoolean(false) | ||
private val robolectricTestRunnerHelper by lazy { JUnit5RobolectricTestRunnerHelper() } | ||
|
||
override fun beforeAll(context: ExtensionContext) { | ||
logger.trace { "beforeAll ${context.requiredTestClass.name}" } | ||
robolectricTestRunnerHelper.createTestEnvironmentForClass(context.requiredTestClass) | ||
} | ||
|
||
override fun beforeEach(context: ExtensionContext) { | ||
logger.trace { "beforeEach ${context.requiredTestClass.name}::${context.requiredTestMethod.name}" } | ||
robolectricTestRunnerHelper.createTestEnvironmentForMethod(context.requiredTestMethod) | ||
robolectricTestRunnerHelper.runOnMainThreadWithRobolectric { | ||
if (!beforeAllFired.getAndSet(true)) { | ||
invokeBeforeAllMethods(testClass = context.requiredTestClass) | ||
} | ||
beforeEach(context.requiredTestMethod) | ||
} | ||
} | ||
|
||
private fun invokeBeforeAllMethods(testClass: Class<*>) { | ||
val beforeAllMethods = testClass | ||
.methods | ||
.filter { | ||
it.getAnnotation(BeforeAll::class.java) != null && | ||
Modifier.isStatic(it.modifiers) | ||
} | ||
|
||
beforeAllMethods.forEach { | ||
logger.trace { "invoke beforeAll ${it.name}" } | ||
|
||
ReflectionUtils.invokeMethod(it, null) | ||
} | ||
} | ||
|
||
override fun afterEach(context: ExtensionContext) { | ||
logger.trace { "afterEach ${context.requiredTestClass.name}::${context.requiredTestMethod.name}" } | ||
robolectricTestRunnerHelper.runOnMainThread { | ||
runWithRobolectric { | ||
afterEach(context.requiredTestMethod) | ||
} | ||
afterEachFinally() | ||
} | ||
} | ||
|
||
override fun afterAll(context: ExtensionContext) { | ||
logger.trace { "afterAll ${context.requiredTestClass.name}" } | ||
robolectricTestRunnerHelper.clearCachedRobolectricTestRunnerEnvironment() | ||
beforeAllFired.set(false) | ||
} | ||
|
||
override fun interceptBeforeAllMethod( | ||
invocation: InvocationInterceptor.Invocation<Void>, | ||
invocationContext: ReflectiveInvocationContext<Method>, | ||
extensionContext: ExtensionContext, | ||
) { | ||
logger.trace { "interceptBeforeAllMethod ${extensionContext.requiredTestClass}" } | ||
invocation.skip() | ||
} | ||
|
||
override fun interceptBeforeEachMethod( | ||
invocation: InvocationInterceptor.Invocation<Void>, | ||
invocationContext: ReflectiveInvocationContext<Method>, | ||
extensionContext: ExtensionContext, | ||
) { | ||
logger.trace { "interceptBeforeEachMethod ${extensionContext.requiredTestClass}::${extensionContext.requiredTestMethod}" } | ||
robolectricTestRunnerHelper.runOnMainThreadWithRobolectric { | ||
super.interceptBeforeEachMethod(invocation, invocationContext, extensionContext) | ||
} | ||
} | ||
|
||
override fun interceptDynamicTest( | ||
invocation: InvocationInterceptor.Invocation<Void>, | ||
invocationContext: DynamicTestInvocationContext, | ||
extensionContext: ExtensionContext, | ||
) { | ||
logger.trace { "interceptDynamicTest ${extensionContext.requiredTestClass}::${extensionContext.requiredTestMethod}" } | ||
robolectricTestRunnerHelper.runOnMainThreadWithRobolectric { | ||
super.interceptDynamicTest(invocation, invocationContext, extensionContext) | ||
} | ||
} | ||
|
||
override fun interceptTestMethod( | ||
invocation: InvocationInterceptor.Invocation<Void>, | ||
invocationContext: ReflectiveInvocationContext<Method>, | ||
extensionContext: ExtensionContext, | ||
) { | ||
logger.trace { "interceptTestMethod ${extensionContext.requiredTestClass}::${extensionContext.requiredTestMethod}" } | ||
robolectricTestRunnerHelper.runOnMainThreadWithRobolectric { | ||
super.interceptTestMethod(invocation, invocationContext, extensionContext) | ||
} | ||
} | ||
|
||
override fun interceptTestTemplateMethod( | ||
invocation: InvocationInterceptor.Invocation<Void>, | ||
invocationContext: ReflectiveInvocationContext<Method>, | ||
extensionContext: ExtensionContext, | ||
) { | ||
logger.trace { "interceptTestTemplateMethod ${extensionContext.requiredTestClass}::${extensionContext.requiredTestMethod}" } | ||
robolectricTestRunnerHelper.runOnMainThreadWithRobolectric { | ||
super.interceptTestTemplateMethod(invocation, invocationContext, extensionContext) | ||
} | ||
} | ||
|
||
override fun interceptAfterEachMethod( | ||
invocation: InvocationInterceptor.Invocation<Void>, | ||
invocationContext: ReflectiveInvocationContext<Method>, | ||
extensionContext: ExtensionContext, | ||
) { | ||
logger.trace { "interceptAfterEachMethod ${extensionContext.requiredTestClass}::${extensionContext.requiredTestMethod}" } | ||
robolectricTestRunnerHelper.runOnMainThreadWithRobolectric { | ||
super.interceptAfterEachMethod(invocation, invocationContext, extensionContext) | ||
} | ||
} | ||
|
||
override fun interceptAfterAllMethod( | ||
invocation: InvocationInterceptor.Invocation<Void>, | ||
invocationContext: ReflectiveInvocationContext<Method>, | ||
extensionContext: ExtensionContext, | ||
) { | ||
logger.trace { "interceptAfterAllMethod ${extensionContext.requiredTestClass}" } | ||
robolectricTestRunnerHelper.runOnMainThreadWithRobolectric { | ||
super.interceptAfterAllMethod(invocation, invocationContext, extensionContext) | ||
} | ||
} | ||
} |
48 changes: 48 additions & 0 deletions
48
...c/test/kotlin/tech/apter/junit/jupiter/robolectric/RobolectricExtensionClassLoaderTest.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
package tech.apter.junit.jupiter.robolectric | ||
|
||
import org.junit.jupiter.api.AfterAll | ||
import org.junit.jupiter.api.BeforeAll | ||
import org.junit.jupiter.api.extension.ExtendWith | ||
import org.robolectric.internal.AndroidSandbox.SdkSandboxClassLoader | ||
import kotlin.test.AfterTest | ||
import kotlin.test.BeforeTest | ||
import kotlin.test.Test | ||
import kotlin.test.assertEquals | ||
|
||
@ExtendWith(RobolectricExtension::class) | ||
class RobolectricExtensionClassLoaderTest { | ||
|
||
@BeforeTest | ||
fun setUp() { | ||
assertSdkClassLoader() | ||
} | ||
|
||
@AfterTest | ||
fun tearDown() { | ||
assertSdkClassLoader() | ||
} | ||
|
||
@Test | ||
fun testClassLoader() { | ||
assertSdkClassLoader() | ||
} | ||
|
||
companion object { | ||
@BeforeAll | ||
@JvmStatic | ||
fun setUpClass() { | ||
assertSdkClassLoader() | ||
} | ||
|
||
@AfterAll | ||
@JvmStatic | ||
fun tearDownClass() { | ||
assertSdkClassLoader() | ||
} | ||
|
||
private fun assertSdkClassLoader() { | ||
val classLoader = Thread.currentThread().contextClassLoader | ||
assertEquals<Class<*>>(SdkSandboxClassLoader::class.java, classLoader.javaClass) | ||
} | ||
} | ||
} |
66 changes: 66 additions & 0 deletions
66
...sion/src/test/kotlin/tech/apter/junit/jupiter/robolectric/RobolectricExtensionSelfTest.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
package tech.apter.junit.jupiter.robolectric | ||
|
||
import android.app.Application | ||
import android.content.Context | ||
import android.os.Looper | ||
import androidx.test.core.app.ApplicationProvider | ||
import java.util.concurrent.atomic.AtomicInteger | ||
import org.junit.jupiter.api.AfterAll | ||
import org.junit.jupiter.api.BeforeAll | ||
import org.junit.jupiter.api.extension.ExtendWith | ||
import org.robolectric.RuntimeEnvironment | ||
import org.robolectric.annotation.Config | ||
import kotlin.test.Test | ||
import kotlin.test.assertEquals | ||
import kotlin.test.assertIs | ||
import kotlin.test.assertNotNull | ||
import kotlin.test.assertTrue | ||
|
||
@ExtendWith(RobolectricExtension::class) | ||
@Config(application = RobolectricExtensionSelfTest.MyTestApplication::class) | ||
class RobolectricExtensionSelfTest { | ||
|
||
@Test | ||
fun shouldInitializeAndBindApplicationButNotCallOnCreate() { | ||
val application = ApplicationProvider.getApplicationContext<Context>() | ||
assertIs<MyTestApplication>(application, "application") | ||
assertTrue("onCreateCalled") { application.onCreateWasCalled } | ||
if (RuntimeEnvironment.useLegacyResources()) { | ||
assertNotNull(RuntimeEnvironment.getAppResourceTable(), "Application resource loader") | ||
} | ||
} | ||
|
||
@Test | ||
fun `Before the test before class should be fired one`() { | ||
assertEquals(1, beforeAllFired.get()) | ||
} | ||
|
||
companion object { | ||
private var onTerminateCalledFromMain: Boolean? = null | ||
private val beforeAllFired = AtomicInteger(0) | ||
|
||
@BeforeAll | ||
@JvmStatic | ||
fun setUpClass() { | ||
beforeAllFired.incrementAndGet() | ||
} | ||
|
||
@AfterAll | ||
@JvmStatic | ||
fun tearDown() { | ||
beforeAllFired.set(0) | ||
} | ||
} | ||
|
||
class MyTestApplication : Application() { | ||
internal var onCreateWasCalled = false | ||
|
||
override fun onCreate() { | ||
this.onCreateWasCalled = true | ||
} | ||
|
||
override fun onTerminate() { | ||
onTerminateCalledFromMain = Looper.getMainLooper().thread === Thread.currentThread() | ||
} | ||
} | ||
} |