mockk a global property in kotlin - android

I am facing the same issue as asked in the below question. please help me out.
Mock a "global" property in Kotlin
I tried solution provided in above question but nothing is working. and I am asking the same question because I am not able to post any comment on the previous question.
I am trying to write test case for below class
class CustomLogger constructor(val ctx: Context, embEnabled: Boolean = false) : Logger {
private val loggers = arrayListOf<Logger>()
fun get() = loggers
init {
if (embEnabled)
loggers.add(Emb(ctx))
if (BuildConfig.DEBUG)
loggers.add(DebugLogger(ctx))
}
override fun logError(t: Throwable, msg: String?) {
loggers.forEach { logger ->
logger.logError(t, msg)
}
}
}
enter code here
Here I am trying to mock get() or init{}

that was on dam question but i got you
note this can only be used in unittest as mockito static mock is not support on Android JVM
testImplementation "org.mockito:mockito-inline:4.8.1" you gonna need
this so added
Update you need to call this i forgot to add it sorry in your test case before call the method
Mockito.mockStatic(Class.forName("com.udacity.project4.locationreminders.RemindersActivityKt"))
fun getMockForMethod(clazz: Class<*>, methodName: String, methodResponse: Any) {
val method: Method = clazz.getMethod(methodName)
Mockito.`when`(method.invoke(null)).thenReturn(methodResponse)
}
now i created the method to handle no argument methods you can modifiy it as you see fit just pass the class using it name
getMockForMethod(Class.forName("com.udacity.project4.locationreminders.RemindersActivityKt"),
"doSomething","New Response")
Assert.assertEquals("New Response", doSomething())
works like a charm Enjoy 😁
i have updated the above code for anyone to use with static members in kotlin
your updates makes this easy to do now it is a class that you can mock entirly and easliy mock any methods
val loggerMock= Mockito.mock(Logger::class.java)
Mockito.`when`(loggerMock.loggers).thenReturn(new array of loggers)

Related

Method isDigitsOnly in android.text.TextUtils not mocked Error

I created a validation use case in which I'm validating the input using isDigitsOnly that use TextUtils internally.
override fun isDigitsOnly(size: String): Boolean {
return !size.trim().isDigitsOnly()
}
when I tried to test it, I got this error
Method isDigitsOnly in android.text.TextUtils not mocked
Does anyone know how I can mock the textUtils in my test class
#RunWith(MockitoJUnitRunner::class)
class ValidationInputImplTest {
#Mock
private lateinit var mMockTextUtils: TextUtils
private lateinit var validationInputImpl: ValidationInputImpl
#Before
fun setUp() {
validationInputImpl = ValidationInputImpl()
}
#Test
fun `contains only digits, returns success`() {
val input = "66"
val result = validationInputImpl(input)
assertTrue(result is ValidationResult.Success)
}
}
At the time of writing the answer,you cannot do that. Because the android code ,that you see is not actual code. You cannot create unit test for that. The default implementation of the methods in android.jar is to throw exception.
One thing you can do is, adding the below in build.gradle file.
But it make the android classes not to throw exception. But it will always return default value. So the test result may actually not work. It is strictly not recommended.
android {
testOptions {
unitTests.returnDefaultValues = true
}
}
The better way to do copy the code from android source and paste the file under src/test/java folder with package name as android.text .
Link to Answer

Using capture and mocks to unit test a class

I am trying to unit test the following class:
class UserProfileDetailsAnalyticUseCaseImp #Inject constructor(private val analyticsProvider: AnalyticsProvider) : UserProfileDetailsAnalyticUseCase {
override fun execute(cdsCustomer: CDSCustomer) {
with(analyticsProvider) {
log(AnalyticEvent.UserId(cdsCustomer.id.toString()))
log(AnalyticEvent.UserEmail(cdsCustomer.email))
}
}
}
And this is my unit test:
class UserProfileDetailsAnalyticUseCaseImpTest {
private lateinit var userProfileDetailsAnalyticUseCaseImp: UserProfileDetailsAnalyticUseCaseImp
private val analyticsProviders: AnalyticsProvider = mock()
#Before
fun setUp() {
userProfileDetailsAnalyticUseCaseImp = UserProfileDetailsAnalyticUseCaseImp(analyticsProviders)
}
#Test
fun `should send analytic event`() {
// Arrange
val cdsCustomer = CDSCustomer(
id = Random.nextInt(0, 100000),
email = UUID.randomUUID().toString())
val userIdCapture= argumentCaptor<AnalyticEvent.UserId>()
val userEmailCapture= argumentCaptor<AnalyticEvent.UserEmail>()
// Act
userProfileDetailsAnalyticUseCaseImp.execute(cdsCustomer)
// Assert
verify(analyticsProviders, atLeastOnce()).log(userIdCapture.capture())
verify(analyticsProviders, atLeastOnce()).log(userEmailCapture.capture())
assertThat(userIdCapture.firstValue.userId).isEqualTo(cdsCustomer.id.toString())
assertThat(userEmailCapture.firstValue.email).isEqualTo(cdsCustomer.email)
}
}
The error I get is the following:
AnalyticEvent$UserId cannot be cast to AnalyticEvent$UserEmail
I am suspecting that because class under test is creating a new object for each log method they will not be the same for the verified methods in the unit test
i.e log(AnalyticEvent.UserId(cdsCustomer.id.toString()))
As a new AnaltyicEvent.UserId will be created and just for the same AnalyticProvider mock
Many thanks for any suggetions
In the documentation of ArgumentCaptor we can read that:
This utility class doesn't do any type checks. The generic
signatures are only there to avoid casting in your code.
Moreover CapturingMatcher which is used for collecting captured arguments has a method which matches all objects:
public boolean matches(Object argument) {
return true;
}
It means that it is normal behaviour and even when we specify concrete type of captor it will record all arguments passed.
Of course all these arguments have to inherit from the same base class because in other case capture method will cause compilation error.
So, both your captors record two arguments.
To fix class cast exception for your test you can assert secondValue for email.
assertThat(userEmailCapture.secondValue.email).isEqualTo(cdsCustomer.email)
You can also stop using argument captors and simply verify invocations of log method.
verify(analyticsProviders).log(AnalyticEvent.UserId(cdsCustomer.id.toString()))
verify(analyticsProviders).log(AnalyticEvent.UserEmail(cdsCustomer.email))

How to mock Kotlin Object in android?

I have an object in kotlin that controls the current user's session information. I want to mock the sign in method which has a call back.
While testing, I need to mock this method in the SessionController object.
object SessionController {
...
fun signIn(username: String, password: String, signInCallBack: SignInCallBack) {
sessionApi.attemptSignIn(username,password,object: SignInCallBack{
override fun onSignInComplete() {
signInCallBack.onSignInComplete()
}
override fun onErrorOccurred(errorCode: Int, errorMessage: String) {
signInCallBack.onErrorOccurred(errorCode)
}
})
}
....
}
The AndroidTest goes something like this:
#RunWith(AndroidJUnit4::class)
class LoginActivityTest {
#Test
fun loginErrorShowing() {
test.tapUsernameField()
test.inputTextinUsernameField("wrongusername")
test.pressUsernameFieldIMEAction()
test.inputTextinPasswordField("randomPassword")
test.pressPasswordFieldIMEAction()
Espresso.onView(ViewMatchers.withId(R.id.errorText)).check(ViewAssertions.matches(withText("Wrong Password")))
}
}
Any suggestions/ideas as to how I can achieve this? I've read online to use Mockk for kotlin but have't been able to mock this method and invoke the appropriate callback. Any suggestions on improving the structure would also be appreciated.
Thanks
Well in my opinion you should made SessionController implementing an interface.
object SessionController: ISessionController {
override fun signIn(username: String, password: String, signInCallBack: SignInCallBack) {
(...)
}
}
interface ISessionController {
fun fun signIn(username: String, password: String, signInCallBack: SignInCallBack)
}
This will give you a lot of possibilities to solve your problem like:
Dependency Injection
Test product flavour
Simple mockk() cration in your test code
It is a bit hard to give you very strict answer because you didn't post any UT code ;)
EDIT
It is hard to cover such a big topic as mocking in one post ;)
Here are some great articles:
Dependency Injection: https://medium.com/#elye.project/fast-mocked-ui-tests-on-android-kotlin-89ed0a8a351a
Using different flavour for tests: https://android-developers.googleblog.com/2015/12/leveraging-product-flavors-in-android.html
Creating Unit Tests you can always do simple:
presenter.sth = mockk<ISessionController>()

Unit testing coroutines on UI thread

I'm using coroutines to do an asynchronous call on pull to refresh like so:
class DataFragment : Fragment(), SwipeRefreshLayout.OnRefreshListener {
// other functions here
override fun onRefresh() {
loadDataAsync()
}
private fun loadDataAsync() = async(UI) {
swipeRefreshLayout?.isRefreshing = true
progressLayout?.showContent()
val data = async(CommonPool) {
service?.getData() // suspending function
}.await()
when {
data == null -> showError()
data.isEmpty() -> progressLayout?.showEmpty(null, parentActivity?.getString(R.string.no_data), null)
else -> {
dataAdapter?.updateData(data)
dataAdapter?.notifyDataSetChanged()
progressLayout?.showContent()
}
}
swipeRefreshLayout?.isRefreshing = false
}
}
Everything here works fine when I actually put it on a device. My error, empty, and data states are all handled well and the performance is good. However, I'm also trying to unit test it with Spek. My Spek test looks like this:
#RunWith(JUnitPlatform::class)
class DataFragmentTest : Spek({
describe("The DataFragment") {
var uut: DataFragment? = null
beforeEachTest {
uut = DataFragment()
}
// test other functions
describe("when onRefresh") {
beforeEachTest {
uut?.swipeRefreshLayout = mock()
uut?.onRefresh()
}
it("sets swipeRefreshLayout.isRefreshing to true") {
verify(uut?.swipeRefreshLayout)?.isRefreshing = true // says no interaction with mock
}
}
}
}
The test is failing because it says that there was no interaction with the uut?.swipeRefreshLayout mock. After some experimenting, it seems this is because I'm using the UI context via async(UI). If I make it just be a regular async, I can get the test to pass but then the app crashes because I'm modifying views outside of the UI thread.
Any ideas why this might be occurring? Also, if anyone has any better suggestions for doing this which will make it more testable, I'm all ears.
Thanks.
EDIT: Forgot to mention that I also tried wrapping the verify and the uut?.onRefresh() in a runBlocking, but I still had no success.
If you want to make things clean and consider using MVP architecture in the future you should understand that CourutineContext is external dependency, that should be injected via DI, or passed to your presenter. More details on topic.
The answer for your question is simple, you should use only Unconfined CourutineContext for your tests. (more)
To make things simple create an object e.g. Injection with:
package com.example
object Injection {
val uiContext : CourutineContext = UI
val bgContext : CourutineContext = CommonPool
}
and in test package create absolutely the same object but change to:
package com.example
object Injection {
val uiContext : CourutineContext = Unconfined
val bgContext : CourutineContext = Unconfined
}
and inside your class it will be something like:
val data = async(Injection.bgContext) {service?.getData()}.await()

How to pass null to an Observable with nullable type in RxJava 2 and Kotlin

I initialize my variable like this:-
val user: BehaviorSubject<User?> user = BehaviorSubject.create()
But I can't do this. IDE throws an error:-
user.onNext(null)
And doing this, IDE says u will never be null:-
user.filter( u -> u!=null)
As Guenhter explained, this is not possible. However, instead of proposing the null-object pattern, I'd recommend an implementation of the Optional type:
data class Optional<T>(val value: T?)
fun <T> T?.asOptional() = Optional(this)
This makes your intent much clearer, and you can use a destructuring declaration in your functions:
Observable.just(Optional("Test"))
.map { (text: String?) -> text?.substring(1)?.asOptional() }
.subscribe()
Using the null-object pattern here can cause more bugs than it solves.
If you use rxkotlin/rxjava 2.0 (I assume so) than the answer is: you can't. The reason is explained here.
This is a break of the interface. Have a look at the Observable Interface
public interface Observer<T> {
/** ... */
void onSubscribe(#NonNull Disposable d);
/** ... */
void onNext(#NonNull T t);
/** ... */
void onError(#NonNull Throwable e);
/** ... */
void onSubscribe(#NonNull Disposable d);
/** ... */
void onNext(#NonNull T t);
/** ... */
void onError(#NonNull Throwable e);
...
The #NonNull will be considered by the Kotlin compiler and therefore you CAN'T pass null.
Even if you could, the onNext would immediately throw an error:
#Override
public void onNext(T t) {
if (t == null) {
onError(new NullPointerException("onNext called with null. Null values are generally not allowed in 2.x operators and sources."));
return;
}
...
}
If you really need such a thing as null you have to fake it. e.g. by creating a static object of User which represents your null-element.
e.g.
data class User(val username, val password) {
companion object {
val NULL_USER = User("", "")
}
}
...
val user = BehaviorSubject.create<User>()
...
user.onNext(User.NULL_USER)
...
user.filter { it !== User.NULL_USER }
But if is somehow possible, try to avoid the null concept and maybe think of another solution where this isn't needed.
Thank you very much for all your answers but I ultimately went with this solution:-
class UserEnvelope(val user:User?) {}
And using this in the observables.
This best suited my requirements.
I am new to Kotlin so I don't know how to use Optionals. But from what I understand, I would have to typecast it to User type everytime I need to observe the values right?
To implement the solution mentioned in the nhaarman's answer, you can use the util class Optional (doc) from the Android SDK which was added in API level 24.
If your app's minSdkVersion less than 24 then you still need to implement it by yourself.
Since RxJava 2 does not support null values, there are some other acceptable solutions you can use:
Work with a custom or third party wrapper library of Optionals like some of the posted answers suggest. When I got rid of Java in favour of Kotlin, Optionals went away in the same package since Kotlin per se supports nullability as part of its type System. Just by this change the code was much more clearer, and I personally don't want to get Optionals back in my code as long as I can avoid them.
Emit Any class instances with your subject type. For example you could create an Empty.INSTANCE enum class which would emulate the null value and then filter by the enum class.
The last one is the one I use and prefer being a variant of the previous solution and is based on specialisations. Our friends of JetBrains always emphasise that classes are very cheap in Kotlin, so this would be a quick example to distinguish logged users and not logged ones:
abstract class SessionUser
sealed class LoggedUser(val username: String, val password: String) : SessionUser()
sealed class LogoutUser : SessionUser()
private val user = BehaviorSubject.create<SessionUser>()
private val loggedUser =
user.filter { it is LoggedUser }.cast(LoggedUser::class.java)
fun login(username: String, password: String) {
user.onNext(LoggedUser(username, password))
}
fun logout() {
user.onNext(LogoutUser())
}
I've taken an approach similar to Optional<User> and UserEnvelope. I make a simple User class and a ReifiedUser class that inherits from it. The User class has a companion object that has a NONE instance. The BehaviorSubject is instantiated with the User.NONE instance. It looks something like this:
open class User {
companion object {
val NONE = User()
}
}
class ReifiedUser(
#field:JsonProperty(J.FirstName) val firstName: String,
#field:JsonProperty(J.LastName) val lastName: String
) : User()
My BehaviorSubject is instantiated like this:
val activeUser: BehaviorSubject<User> = BehaviorSubject.createDefault(User.NONE)
And wherever I need to use activeUser I either flatMap it to Observable.empty() if it's NONE or just figure out what it is and what to do in the subscriber.
I don't like mixing java Optional with kotlin nullable because mixing map and let gets really confusing and ugly. This way it's very obvious what's going on.
I think it makes more sense to write a container class such as Result. An example of that would be
data class Result<T>(value: T?, error: Throwable?)
Usage
Observable.create { observer ->
upstreamService.listen(object: UpstreamListener {
onSuccess(data: User) {
observer.onSuccess(Result(data))
}
onError(exception: Throwable) {
observer.onSuccess(Result(null, exception))
}
}
}

Categories

Resources