Using mockk to match any varargs - android

I'm trying to mock an Android Context to return a string from a resource id. However I have trouble matching the stub to the call, I assume it is because of the varargs. However I am new to mockk so I might just miss something very easy.
I mock the context this way:
val context = mockk<Context>()
every { context.getString(any(), any()) } returns stringToReturn
But when calling getString on the object it throws the following exception:
io.mockk.MockKException: no answer found for: Context(#1).getString(2131689544, [])
If it is important, I call the function in the class under test similar to this. formatArgs may be empty but doesn't have to:
protected fun foo(stringResource: Int, vararg formatArgs: Any) {
val s = context.getString(errorMessageStringResource, *formatArgs)
Any idea how I can fix this?
You can check the project and reproduce the exception here: Github Project

Version 1.9.1 introduces few additional matchers to match varargs.
https://mockk.io/#varargs

There is a related open issue in mockk v1.9: https://github.com/mockk/mockk/issues/224 (see referenced issues as well)
I tried several solutions but I ended up creating overloaded functions just for testing with mockk, eg.
class Context {
// Renamed because of same JVM signature
fun foo2(stringResource: Int, vararg formatArgs: Any) = foo(stringResource, formatArgs)
// Function accepts
fun foo(stringResource: Int, formatArgs: args: Array<out Any>) = ...
}
then test the non-vararg foo() function with mockk.
I know it's an ugly workaround but if you find a better one please let me know :)

Related

Kotlin - Issue Extending Kotlin String Class

I am currently trying to extend Kotlins String class with a method in a file StringExt.kt
fun String.removeNonAlphanumeric(s: String) = s.replace([^a-ZA-Z0-9].Regex(), "")
But Kotlin in not allowing me to use this method in a lambda:
s.split("\\s+".Regex())
.map(String::removeNonAlphanumeric)
.toList()
The error is:
Required: (TypeVariable(T)) -> TypeVariable(R)
Found: KFunction2<String,String,String>
What confuses me about this is that Kotlins Strings.kt has very similar methods and
I can call them by reference without Intellij raising this kind of issue. Any advice is appreciated.
This is because you have declared an extension function that accepts an additional parameter and should be used as s.replace("abc").
I think what you meant is the following:
fun String.removeNonAlphanumeric(): String = this.replace("[^a-ZA-Z0-9]".toRegex(), "")
This declaration doesn't have an extra parameter and uses this to refer to the String instance it is called on.
I thing this is because a lambda is an anonymous function and dose not access to the scope of a extension file.
Check this link maybe contains some useful information:
https://kotlinlang.org/docs/reference/extensions.html

How to make a function that returns a generic object?

I am trying to make a function that returns a T/Any object. The only thing is that i am not quite sure how to do it.
fun readMockData(context: Context, filename: String): Any {
val json = context.assets.open("$filename.json").bufferedReader().use(BufferedReader::readText)
return RetrofitSingleton.GSON.fromJson(json, object : TypeToken<Any>() {}.type)
}
This is what i tried at first, but i am getting an error.
How am i supposed to make this function properly?
If I understand your problem correctly, this is what you need:
#OptIn(ExperimentalStdlibApi::class)
inline fun <reified T> readMockData(context: Context, filename: String): T {
val json = context.assets.open("$filename.json").bufferedReader().use(BufferedReader::readText)
return RetrofitSingleton.GSON.fromJson(json, typeOf<T>().javaType)
}
Note it has to be inline, because otherwise there is no way to acquire a type. If your function is longer than above, it may be a good idea to split it into an inline reified wrapper and the main function that accepts Type as a parameter.
It uses reflection, so you need to add a dependency to it. Also, typeOf() is experimental, but from my experience it just works (at least on JVM) and it is there for a long time, so I guess it won't change.

Type inference failed: Not enough information to infer parameter T in fun <T : Context!> getApplicationContext(): T! Please specify it explicitly

I am trying to write some tests for my android app and it's really chalanging for me. One of many obsticles is this error
Type inference failed: Not enough information to infer parameter T in fun <T : Context!> getApplicationContext(): T! Please specify it explicitly.
which occures on this line
val actualIntent: Intent = shadowOf(ApplicationProvider.getApplicationContext())
.nextStartedActivity
Full test code looks like this
#Test
fun clickingLogin_shouldStartLoginActivity() {
val scenario = launch(LogInActivity::class.java)
scenario.onActivity { activity ->
activity.go_to_register_button.performClick()
val expectedIntent = Intent(activity, RegistrationActivity::class.java)
val actual: Intent = shadowOf(ApplicationProvider.getApplicationContext())
.nextStartedActivity
expectedIntent.component shouldBeEqualTo actual.component
}
}
Basically the shadowOf function is overloaded and can return many thinks and I need to specify the type.
I believe it should be somethink like shadowOf<SomeType>(...) But I have no idea what the actual type should be.
Any help would be really appreciated.
EDIT
I am following roboloctric guideline but trying to write it in an androidX way
An Intent is a different type of Object that does not extend from Context.
this line :
val actualIntent: Intent = shadowOf(ApplicationProvider.getApplicationContext())
provides a Context as an argument, and returns a ShadowContext, not an Intent.
docs ref : http://robolectric.org/javadoc/3.0/org/robolectric/Shadows.html#shadowOf-android.content.Context-
Basically it is telling you that a tree can't be a type of car.
I may not have asked as clearly as I should have. But for anyone who landed here stucked with the same problem here is a solution.
I did not figure out how to test if an activity is producing correct intents in normal tests. But in instrumented tests it goes like this:
#get:Rule
var activityRule: IntentsTestRule<MyActivity> =
IntentsTestRule(MyActivity::class.java)
#Test
fun testIntent () {
// perform some actions
// than verify
intended(hasComponent(OtherActicity::class.qualifiedName))
intended(hasExtra(A_CONSTANT, someValue))
}
You need a dependency for this to work
androidTestImplementation 'androidx.test.espresso:espresso-intents:3.1.0'
more info here

Return type is 'Unit?', which is not a subtype of overridden

Today while programming I found some odd behaviour in Kotlin. I could easily go around it, but I wonder if there is some reason to it or if it is a bug in Kotlin.
I have the following interface of a delegate which delegates the showing of a dialog to the Activity.
interface ViewModelDelegate {
fun showWarningDialog(textResource: Int)
}
I want to implement it as following in the Activity. Since I know I can only do it with a context and the Activity.getContext() may return null, I wrap the code in context?.let
override fun showWarningDialog(textResource: Int) = context?.let {
//show dialog
}
However this gives me a compile error:
Return type of 'showWarningDialog' is not a subtype of the return type of the overridden member 'public abstract fun showWarningDialog(textResource: Int): Unit defined in com.some.class.path'
Which really confused me, because I don't want to return anything. So since let returns whatever the function inside returns, I was wondering if I could fix it by writing a version of let which does not return anything.
fun <T, R> T.myLet(block: (T) -> R) {
let(block)
}
However this did not remove the compiler error. I found then that the mouseover text over the error gives more information (would be nice if the compiler did). It says:
Return type is 'Unit?', which is not a subtype of overridden
Now that tells me more about the problem. Because the function context?let call may not happen, it could return null. Now there are multiple ways to go around this. I could add ?: Unit too the end of the function call or I could define showWarningDialog to return Unit? which will allow me to call it just fine in most cases. However none of these solutions are desireable. I will probably just make a normal method and call the let inside of that instead of delegating the call to it. Costs me another level of indentation and an extra vertical line:
override fun showWarningDialog(textResource: Int) {
context?.let {
//show dialog
}
}
My question is, is this behaviour intended? Why or when would this be useful that a function that returns Unit cannot be delegated to an optional function call. I am very confused by this behaviour.
Single expression function
fun foo() = <expression>
by language design is equivalent to
fun foo(): <ReturnType> {
return <expression>
}
And because Unit? is not a not a subtype of Unit, you can't return it in from a function, which returns Unit. In this sense Unit just another type in the type system, it's not something magical. So it works just as it's supposed to work with any other type.
Why or when would this be useful that a function that returns Unit cannot be delegated to an optional function call.
So basically the question is why language designers did not created a special handling to accept Unit? from a function declaring Unit as a return type. I can think about a few reasons:
It requires to create this special handling in the compiler. Special cases lead to bugs, break slim language design and complicate documentation.
As it had to be a special case, it would be not really clear and predictable for programmers. Currently it works in the same way for all types, no special treatments. It makes the language predictable, you don't need to check the documentation for every type to see if it's treated specially.
It also adds some additional safety, so to make you notice that your expression can actually skip the calculation.
So trying to summarize, I would say making this case work does not add much of value but can potentially bring some issues. That's probably why they did not add it to the language.
lets discuss this case when you have return type for example String
interface someInterface{
fun somFun():String
}
class someClass : someInterface {
var someString:String? = null
override fun somFun()=someString?.let {
//not working
it
}
override fun somFun()=someString?.let {
//working
it
}?:""
}
so what we see that when parents return type is String you cannot return Strin? it is jus kotlins nullSafety ,
what is different when you don't have return type ? lets change the code above a little
interface someInterface{
fun somFun():String
fun unitFun()
}
class someClass : someInterface {
var someString:String? = null
override fun unitFun() {
//if it is possible to return null in here
}
override fun somFun()=someString?.let {
val someresult = unitFun().toString() //you will get crash
it
}?:""
}
now we have another function without return type (unitFun Unit)
so if you can return Unit? in your subclass it will cause a crash when you want to use the result of method because it is defined asUnit and you dont need any null checks.
generally it means Unit is also type and you need to keep it null safe .

How to make an extension functions about getInteger for Context in Kotlin?

I'm a beginner of Kotlin, the Code A get a int value from resource file.
I hope to use an extension functions to do it, and invoke it just like this.getInteger(R.integer.ActivityEditBackup)
But Code B I made is incorrect, how can I fix it?
Code A
mContext.resources.getInteger(R.integer.ActivityEditBackup))
Code B
inline fun <reified T : Activity>Context.getInteger(int id): int {
return T.resources.getInteger(id)
}
You're overcomplicating it a bit.
You won't use the specific type of Context in any way, you don't need to make your extension generic.
In the parameter list, the name of the parameter comes first, and the type after.
The integer type's name in Kotlin is Int, with a capital I.
You can refer to your Context inside the extension function with this.
You can use support annotations to specify that your parameter is always an integer resource ID.
Overall, with these changes:
inline fun Context.getInteger(#IntegerRes id: Int): Int {
return this.resources.getInteger(id)
}
There was also some general confusion about syntax, you should look into the documentation for functions and then extensions.
Additionally, you can convert the function to an expression body and omit the explicit this:
inline fun Context.getInteger(#IntegerRes id: Int) = resources.getInteger(id)

Categories

Resources