Last active
May 6, 2025 15:57
-
-
Save AniFichadia/0a0aa665dfc554e963d2ab3276247efb to your computer and use it in GitHub Desktop.
JUnit classes that skip subsequent tests if any test in a test suite fails
This file contains hidden or 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
import org.junit.internal.AssumptionViolatedException | |
import org.junit.rules.TestRule | |
import org.junit.runner.Description | |
import org.junit.runners.model.Statement | |
/** | |
* @author Aniruddh Fichadia | |
* @date 2020-04-03 | |
*/ | |
class SkipOnFirstFailureTestRule( | |
var enabled: Boolean = false | |
) : TestRule { | |
override fun apply(base: Statement, description: Description): Statement = object : Statement() { | |
@Throws(Throwable::class) | |
override fun evaluate() { | |
onTestStarted(description) | |
try { | |
base.evaluate() | |
} catch (e: AssumptionViolatedException) { | |
// Referring to org.junit.rules.TestWatcher, AssumptionViolatedException's are thrown when a test has been skipped / ignored | |
} catch (e: Throwable) { | |
onTestFailed(e, description) | |
// Propagate the original exception | |
throw e | |
} | |
} | |
} | |
@Throws(AssumptionViolatedException::class) | |
private fun onTestStarted(description: Description) { | |
if (enabled) { | |
failedTestName?.let { failedTestName -> | |
// Imitate a test skip if there's been a previously failed test | |
throw AssumptionViolatedException("$failedTestName is the first test to fail, skipping ${getTestName(description)}") | |
} | |
} | |
} | |
private fun onTestFailed(e: Throwable, description: Description) { | |
if (enabled && failedTestName == null) { | |
failedTestName = getTestName(description) | |
} | |
} | |
private fun getTestName(description: Description) = "${description.className}.${description.methodName}" | |
private companion object { | |
// Store in a companion object to allow persistence across test classes. This is a bit lazy, but it works. | |
var failedTestName: String? = null | |
} | |
} |
This file contains hidden or 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
import androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner | |
import org.junit.runner.Description | |
import org.junit.runner.Runner | |
import org.junit.runner.manipulation.Filter | |
import org.junit.runner.manipulation.Filterable | |
import org.junit.runner.manipulation.Sortable | |
import org.junit.runner.manipulation.Sorter | |
import org.junit.runner.notification.Failure | |
import org.junit.runner.notification.RunListener | |
import org.junit.runner.notification.RunNotifier | |
import org.junit.runners.model.FrameworkMethod | |
/** | |
* @author Aniruddh Fichadia | |
* @date 2020-04-03 | |
*/ | |
class SkipOnFirstFailureTestRunner(klass: Class<*>) : Runner(), Filterable, Sortable { | |
private val enabled: Boolean = true | |
// Note: Currently this does not support RobolectricTestRunner, but a similar check may be added as AndroidJUnit4.loadRunner(...) | |
private val delegateRunner = SkipOnFirstFailureAndroidJUnit4ClassRunner(klass) | |
override fun run(notifier: RunNotifier) { | |
if (enabled) { | |
notifier.addListener(object : RunListener() { | |
override fun testFailure(failure: Failure) { | |
super.testFailure(failure) | |
// Listen for test failures | |
testHasFailed = true | |
} | |
}) | |
} | |
delegateRunner.run(notifier) | |
} | |
override fun getDescription(): Description = delegateRunner.description | |
override fun filter(filter: Filter) = (delegateRunner as Filterable).filter(filter) | |
override fun sort(sorter: Sorter) = (delegateRunner as Sortable).sort(sorter) | |
private class SkipOnFirstFailureAndroidJUnit4ClassRunner(klass: Class<*>) : AndroidJUnit4ClassRunner(klass) { | |
override fun runChild(method: FrameworkMethod, notifier: RunNotifier) { | |
if (!testHasFailed) { | |
super.runChild(method, notifier) | |
} else { | |
notifier.fireTestIgnored(describeChild(method)) | |
} | |
} | |
} | |
private companion object { | |
// Store in a companion object to allow persistence across test classes. This is a bit lazy, but it works. | |
var testHasFailed: Boolean = false | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment