Skip to content

Instantly share code, notes, and snippets.

@Razeeman
Last active December 6, 2019 11:33
Show Gist options
  • Save Razeeman/9453d78f223c8660f7cc8ca73e9b3638 to your computer and use it in GitHub Desktop.
Save Razeeman/9453d78f223c8660f7cc8ca73e9b3638 to your computer and use it in GitHub Desktop.
Util class for unit tests. Creates subject and mocks instances, resets mocks before each test. Applies Rx rule.
/**
* Util class for unit tests.
* Creates subject and mocks instances, resets mocks before each test. Applies Rx rule.
*/
abstract class SubjectDelegateUnitTest<T : Any> : BaseUnitTest() {
@PublishedApi internal var mocks = mutableListOf<Any>()
lateinit var subject: T
override fun setUp() {
super.setUp()
reset(*mocks.toTypedArray())
}
/**
* Should be called in test init block or before each test.
*/
inline fun <reified T2> prepareForTest() {
processConstructor(T2::class.java)
processFields(T2::class.java)
}
inline fun <reified T3> getMock(): T3 {
return mocks.find { it is T3 } as? T3
?: throw IllegalArgumentException("Cannot find mock instance of ${T3::class.java.simpleName}")
}
// Mock constructor parameters
@PublishedApi internal fun processConstructor(clazz: Class<*>) {
val constructor = clazz.constructors[0]
val params = constructor.parameterTypes.map(::processClass)
mocks.addAll(params.filter { canBeMocked(it::class.java) })
@Suppress("UNCHECKED_CAST")
subject = constructor.newInstance(*params.toTypedArray()) as? T
?: throw IllegalArgumentException("Cannot create subject instance")
}
// Mock injectable fields
@PublishedApi internal fun processFields(clazz: Class<*>) {
clazz.fields.forEach { field ->
processField(field)?.let { param ->
if (canBeMocked(param::class.java)) mocks.add(param)
field.set(subject, param)
}
}
}
private fun processClass(clazz: Class<*>): Any {
return if (canBeMocked(clazz)) Mockito.mock(clazz, Mockito.RETURNS_SMART_NULLS)
else when (clazz) {
// Add more types if necessary
String::class.java -> TEST_STRING
else -> throw IllegalArgumentException("Cannot mock this class ${clazz.simpleName}")
}
}
private fun processField(field: Field): Any? =
if (fieldInjected(field)) processClass(field.type) else null
private fun canBeMocked(clazz: Class<*>): Boolean =
MockUtil.typeMockabilityOf(clazz).mockable()
private fun fieldInjected(field: Field): Boolean =
field.annotations.find { it.annotationClass.toString().contains("javax.inject.Inject") } != null
companion object {
@JvmField @ClassRule
val rule = Rules.RxImmediateSchedulerRule()
}
}
abstract class BaseUnitTest {
companion object {
const val TEST_BOOLEAN = true
const val TEST_STRING = "test_string"
const val TEST_LONG = 123L
const val TEST_INT = 45
const val TEST_FLOAT = 6.7f
const val TEST_DOUBLE = 8.9
const val TEST_EMPTY_STRING = ""
const val TEST_ID = "test_id"
const val TEST_LOGIN = "test_login"
const val TEST_PASSWORD = "test_password"
const val TEST_CODE = "test_code"
const val TEST_USER_TOKEN = "user_token"
const val TEST_PHONE = "test_phone"
const val TEST_URL = "test_url"
const val TEST_EMAIL = "test_email"
}
@Before
open fun setUp() {}
@After
open fun tearDown() {}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment