Test Koin Android modules loading - android-koin

I work on a project with Koin (I'm not familiar with Koin).
I have update Koin from 3.0.2 to 3.2.2 and the test starts to fail.
class KoinDependenciesTest : KoinTest() {
#get:Rule
val koinTestRule = KoinTestRule.create {
val mockApplication = mock<Application>()
androidContext(mockApplication)
// The Level.ERROR added to prevent exception java.lang.NoSuchMethodError
printLogger(Level.ERROR)
modules(modulesList)
declareMock<PreferencesManager>()
declareMock<ConnectivityInterceptor>()
declareMock<SharedPreferences>()
declareMock<SharedPrefsUtils>()
declareMock<CrashlyticsCustomLogger>()
declareMock<PushNotificationManager>()
val dateTime: DateTime = mock()
declareMock<DateManager> {
whenever(now()).thenReturn(dateTime)
whenever(dateTime.minusDays(any())).thenReturn(dateTime)
whenever(dateTime.toDate()).thenReturn(mock())
}
}
#get:Rule
val mockProvider = MockProviderRule.create { clazz ->
Mockito.mock(clazz.java)
}
#get:Rule
val trampolineSchedulerRule = TrampolineSchedulerRule()
#get:Rule
var instantTaskExecutorRule = InstantTaskExecutorRule()
#ExperimentalCoroutinesApi
#get:Rule
// use to call runBlockingTest {} for components with coroutine functions
val coroutinesDispatcherRule: CoroutinesMainDispatcherRule = CoroutinesMainDispatcherRule()
#ObsoleteCoroutinesApi
#OptIn(ExperimentalCoroutinesApi::class)
#Test
fun `Checking dependencies loading`() {
checkModules {
modules(modulesList)
}
}
}
I get an error
A Koin Application has already been started
I fixed it by change KoinTest to AutoCloseKoinTest
Now i can't pass the Checking dependencies loading. Error
Could not create instance for....
...
Caused by: org.koin.core.error.NoBeanDefFoundException
I have much more classes then defined in declareMock and a few additional functions to test initialisations of different classes.
How I can test modules loading?

Related

Instantiate Hilt Worker for Instrumentation Testing?

I am having problem trying to initialize my WorkerGetData class for instrumentation testing. I have done the following:
removed the default work initializer in manifest file.
added configuration provider in the Application class.
called WorkManagerTestInitHelper in the Test file.
added kapt "androidx.hilt:hilt-compiler:1.0.0" to app module.
added kapt "com.google.dagger:hilt-compiler:2.42" to app module.
But still get the error java.lang.NoSuchMethodException: com.example.myproject.WorkerGetData.<init> [class android.content.Context, class androidx.work.WorkerParameters].
I am using work manager version 2.7.1.
Code
<provider
android:name="androidx.startup.InitializationProvider"
android:authorities="${applicationId}.androidx-startup"
android:exported="false"
tools:node="merge">
<meta-data
android:name="androidx.work.WorkManagerInitializer"
android:value="androidx.startup"
tools:node="remove" />
</provider>
#HiltAndroidApp
class Application : android.app.Application(), Configuration.Provider {
#Inject
lateinit var workerFactory: HiltWorkerFactory
override fun getWorkManagerConfiguration() =
Configuration.Builder()
.setWorkerFactory(workerFactory)
.build()
override fun onCreate() {
super.onCreate()
}
}
#HiltWorker
class WorkerGetData #AssistedInject constructor(
val repository: MyRepository,
#Assisted val context: Context,
#Assisted workerParameters: WorkerParameters,
): CoroutineWorker(context, workerParameters) {
override suspend fun doWork(): Result {
val data = repository.getData()
return Result.success(data)
}
}
#Test
fun testGetDataWorker() {
val request = OneTimeWorkRequestBuilder<WorkerGetData>()
.build()
val workManager = WorkManager.getInstance(context)
workManager.enqueue(request).result.get() /*<-----------------------error here*/
val workInfo = workManager.getWorkInfoById(request.id).get()
assertThat(workInfo.state).isEqualTo(WorkInfo.State.SUCCEEDED)
}
There was a 6th item which I had forgot to do. I needed to create a WorkerFatory() so that Hilt could correctly inject WorkerGetData constructor. There is a great article from Pietro Maggi on how to do this:
https://medium.com/androiddevelopers/customizing-workmanager-with-dagger-1029688c0978

How to create a Page/Screen Object Model in Jetpack Compose Testing

For basic testing, if I create a test class like below, it works fine.
class MyComposeTest {
#get:Rule
val composeTestRule = createAndroidComposeRule<MainActivity>()
#Test
fun myTest() {
composeTestRule.onNodeWithText("Login").performClick()
composeTestRule.onNodeWithText("Home").assertIsDisplayed()
}
}
But what if i want to abstract some of these into separate classes for an end-to-end test?
e.g. I want to create a login page class with all locators for Login and similarly for Home page and simplify my test as
#Test
fun myTest() {
val login = LoginPage()
val home = HomePage()
login.loginBtn.performClick()
home.homeTxt.assertIsDisplayed()
}
I am not sure how my page classes (with locators) should look like to make this possible.
You should pass the composeTestRule in the page's constructor. The code would look like this:
class BaseTestSuite {
#get:Rule
val composeTestRule = createAndroidComposeRule<MainActivity>()
}
class LoginPage(composeTestRule: ComposeContentTestRule) {
val loginBtn = onNodeWithText("Login")
fun tapLoginButton() {
loginBtn.performClick()
}
}
class MyTestSuite() : BaseTestSuite {
val loginPage = LoginPage(composeTestRule)
#Test
fun myTest() {
loginPage.tapLoginButton()
// rest of the code
}
}

Youtube player support fragment no longer working on Android studio 3.2 (androidx)

I just updated my Android Studio to version 3.2 and followed instructions to use androidx.
I've been using a Youtube fragment inside a Fragment activity and everything worked perfectly but, after the update, these 3 simple lines now give me the error "Cannot resolve method 'add(...)'":
YouTubePlayerSupportFragment youTubePlayerFragment = YouTubePlayerSupportFragment.newInstance();
FragmentTransaction transaction = getChildFragmentManager().beginTransaction();
transaction.add(R.id.youtube_fragment, youTubePlayerFragment).commit();
...and when i try to use "replace" instead of "add" it says: "Wrong 2nd argument type. Found: 'com.google.android.youtube.player.YouTubePlayerSupportFragment', required: 'androidx.fragment.app.Fragment'"
...which makes me think that the problem has to do with the new AndroidX feature.
The problem is that the add method wants the second parameter of type:
androidx.fragment.app.Fragment
...but the YouTubePlayerSupportFragment returns a:
android.support.v4.app.Fragment
Does anyone know how to solve this problem?
Is there a way to cast the "android.support.v4.app.Fragment" into the "androidx.fragment.app.Fragment"?
Just use transaction.replace. Ignore the error, it'll work. Google hasn't refactored youtube api library to androidx yet.
Just copy the original java file (com.google.android.youtube.player.YouTubePlayerFragment) to your project to the same package but different class name etc. com.google.android.youtube.player.YouTubePlayerFragmentX, and update the extends class from android.app.Fragment to androidx.fragment.app.Fragment.
The implementation is the same:
YouTubePlayerFragmentX youTubePlayerFragment = YouTubePlayerFragmentX.newInstance();
FragmentTransaction transaction = getChildFragmentManager().beginTransaction();
transaction.add(R.id.youtube_fragment, youTubePlayerFragment).commit();
Tested... it's working.
I've fixed it by following the #Hosszful answer,
I made it easy by just using this file, https://gist.github.com/medyo/f226b967213c3b8ec6f6bebb5338a492
Replace .add
transaction.add(R.id.youtube_fragment, youTubePlayerFragment).commit();
with this .replace
transaction.replace(R.id.youtube_fragment, youTubePlayerFragment).commit();
and copy this class to your project folder (it may need to create the following folders)
java -> com -> google -> android -> youtube -> player -> (here name of)
YouTubePlayerSupportFragmentX.java
then in code replace
YouTubePlayerSupportFragment
to
YouTubePlayerSupportFragmentX.
Many thanks to both #Hosszuful and #Mehdi. I have followed your advice and it worked very nicely.
A few weeks after I asked this question I "translated" my app to Kotlin and, therefore, I tried to translate your answer as well.
This is what I ended up with and it's working for me.
package com.google.android.youtube.player //<--- IMPORTANT!!!!
import android.os.Bundle
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import com.google.android.youtube.player.internal.ab
import java.util.*
class YouTubePlayerSupportFragmentX : Fragment(), YouTubePlayer.Provider {
private val a = ViewBundle()
private var b: Bundle? = null
private var c: YouTubePlayerView? = null
private var d: String? = null
private var e: YouTubePlayer.OnInitializedListener? = null
override fun initialize(var1: String, var2: YouTubePlayer.OnInitializedListener) {
d = ab.a(var1, "Developer key cannot be null or empty")
e = var2
a()
}
private fun a() {
if (c != null && e != null) {
c?.a(this.activity, this, d, e, b)
b = null
e = null
}
}
override fun onCreate(var1: Bundle?) {
super.onCreate(var1)
b = var1?.getBundle("YouTubePlayerSupportFragment.KEY_PLAYER_VIEW_STATE")
}
override fun onCreateView(var1: LayoutInflater, var2: ViewGroup?, var3: Bundle?): android.view.View? {
c = YouTubePlayerView(Objects.requireNonNull(this.activity), null, 0, a)
a()
return c
}
override fun onStart() {
super.onStart()
c?.a()
}
override fun onResume() {
super.onResume()
c?.b()
}
override fun onPause() {
c?.c()
super.onPause()
}
override fun onSaveInstanceState(var1: Bundle) {
super.onSaveInstanceState(var1)
(if (c != null) c?.e() else b)?.let { var2 ->
var1.putBundle("YouTubePlayerSupportFragment.KEY_PLAYER_VIEW_STATE", var2)
}
}
override fun onStop() {
c?.d()
super.onStop()
}
override fun onDestroyView() {
this.activity?.let { c?.c(it.isFinishing) }
c = null
super.onDestroyView()
}
override fun onDestroy() {
if (c != null) {
val var1 = this.activity
c?.b(var1 == null || var1.isFinishing)
}
super.onDestroy()
}
private inner class ViewBundle : YouTubePlayerView.b {
override fun a(var1: YouTubePlayerView, var2: String, var3: YouTubePlayer.OnInitializedListener) {
e?.let { initialize(var2, it) }
}
override fun a(var1: YouTubePlayerView) {}
}
companion object {
fun newInstance(): YouTubePlayerSupportFragmentX {
return YouTubePlayerSupportFragmentX()
}
}
}
There may be better ways to write it down and any help on that regard would be mostly appreciated but, if anyone else was looking for the Kotlin version of this problem's solution, this code should do.
PS: I'm gonna leave #Mehdi's answer as the accepted one because he's also sharing credits with #Hosszuful and because my answer is just the Kotlin version of what they suggest.
I got it working by following code chunk.
Object obj =
getSupportFragmentManager().findFragmentById(R.id.youtube_player_fragment);
if (obj instanceof YouTubePlayerSupportFragment)
youTubePlayerFragment = (YouTubePlayerSupportFragment) obj;
During debugging I found that the fragmentmanager was coming to be instance of YouTubePlayerSupportFragment only. But compiler was not able to cast it when I would write
(YouTubePlayerSupportFragment)
getSupportFragmentManager().findFragmentById(R.id.youtube_player_fragment);
The above code chunk (instanceof ) worked fine.
Suggested solutions did not work, till I tried the comment from Bek: Pierfrancesco Soffritti's android-youtube-player that is maintained and works without a hitch.

Applying Groovy extensions in Grails produces MissingMethodException for String#toBoolean()

Background
Groovy have the feature of adding methods to the existing classes, and I've found some interesting ones.
Then I discovered that I need to customize my Grails bootstrap to load them, so I add:
def init = { servletContext -> addExtensionModules() }
def addExtensionModules() {
Map<CachedClass, List<MetaMethod>> map = [:]
ClassLoader classLoader = Thread.currentThread().contextClassLoader
try {
Enumeration<URL> resources = classLoader.getResources(MetaClassRegistryImpl.MODULE_META_INF_FILE)
for (URL url in resources) {
if (url.path.contains('groovy-all')) {
// already registered
continue
}
Properties properties = new Properties()
InputStream inStream
try {
inStream = url.openStream()
properties.load(inStream)
GroovySystem.metaClassRegistry.registerExtensionModuleFromProperties(properties,
classLoader, map)
}
catch (IOException e) {
throw new GroovyRuntimeException("Unable to load module META-INF descriptor", e)
} finally {
inStream?.close()
}
}
} catch (IOException ignored) {}
map.each { CachedClass cls, List<MetaMethod> methods ->
cls.setNewMopMethods(methods)
}
}
And I add in my BuildConfig.groovy
compile ('ca.redtoad:groovy-crypto-extensions:0.2') {
excludes 'groovy-all'
}
The Question
The problem is that now I cannot use the toBoolean() method of Groovy String:
groovy.lang.MissingMethodException: No signature of method:
java.lang.String.toBoolean() is applicable for argument types: ()
values: [] Possible solutions: asBoolean(), asBoolean(), toFloat(),
toDouble()
Since groovy is already registered, why the method is missing? I'm using Grails 2.2.4.
EDIT
Tested in a groovy 2.0.8 console, and the code works, so probably is something related to Grails.
#Grab('ca.redtoad:groovy-crypto-extensions:0.2')
#GrabExclude('org.codehaus.groovy:groovy-all')
addExtensionModules() //same method of BootStrap, ommited to make shorter.
def key = "password".toKey()
def ciphertext = "some plaintext".bytes.encrypt(key: key)
def x = new String(ciphertext.decrypt(key: key)).toBoolean()
println "S".toBoolean()
Replace
map.each { CachedClass cls, List<MetaMethod> methods ->
cls.setNewMopMethods(methods)
}
with
map.each { CachedClass cls, List<MetaMethod> methods ->
//Add new MOP methods instead of set them as new
cls.addNewMopMethods(methods)
}
When new meta method is set in the CachedClass the existing extensions/meta methods are overriden by the only provided extension from the extension module. In this case, groovy-crypto-extension uses the below extension methods on String class
class java.lang.String=
[public static javax.crypto.spec.SecretKeySpec ca.redtoad.groovy.extensions.crypto.CryptoExtensionMethods.toKey(java.lang.String),
public static javax.crypto.spec.SecretKeySpec ca.redtoad.groovy.extensions.crypto.CryptoExtensionMethods.toKey(java.lang.String,java.util.Map)
]
If those methods are set to the CachedClass, the existing methods are wiped out. So it has to be replace with adding them to the CachedClass. Hence, making toBoolean available on String class.
Challenge accepted and executed. You owe me a treat. (Gift Card is acceptable too). ;)

binding and closures groovy

I don't know how to use binding with closures in Groovy. I wrote a test code and while running it, it said, missing method setBinding on the closure passed as parameter.
void testMeasurement() {
prepareData(someClosure)
}
def someClosure = {
assertEquals("apple", a)
}
void prepareData(testCase) {
def binding = new Binding()
binding.setVariable("a", "apple")
testCase.setBinding(binding)
testCase.call()
}
This works for me with Groovy 1.7.3:
someClosure = {
assert "apple" == a
}
void testMeasurement() {
prepareData(someClosure)
}
void prepareData(testCase) {
def binding = new Binding()
binding.setVariable("a", "apple")
testCase.setBinding(binding)
testCase.call()
}
testMeasurement()
In this script example, the setBinding call is setting a in the scripts binding (as you can see from the Closure documentation, there is no setBinding call). So after the setBinding call, you can call
println a
and it will print out "apple"
So to do this in the class, you can set the delegate for the closure (the closure will revert back to this delegate when a property cannot be found locally) like so:
class TestClass {
void testMeasurement() {
prepareData(someClosure)
}
def someClosure = { ->
assert "apple" == a
}
void prepareData( testCase ) {
def binding = new Binding()
binding.setVariable("a", "apple")
testCase.delegate = binding
testCase.call()
}
}
And it should grab the value fro a from the delegate class (in this case, a binding)
This page here goes through the usage of delegate and the scope of variables in Closures
Indeed, instead of using a Binding object, you should be able to use a simple Map like so:
void prepareData( testCase ) {
testCase.delegate = [ a:'apple' ]
testCase.call()
}
Hope it helps!
This is really strange behaviour: removing the "def" in front of someClosure declaration makes the script work in JDK1.6 Groovy:1.7.3
Update: This was posted in the answer above. My mistake to repeat it.
Update: Why it works? Without a def first line is taken as a property assignment which calls setProperty and makes the variable available in binding, which is resolved later.
a def should have worked as well as per (http://docs.codehaus.org/display/GROOVY/Groovy+Beans)
someClosure = {
assert "apple", a
print "Done"
}
void testMeasurement() {
prepareData(someClosure)
}
void prepareData(testCase) {
def binding = new Binding()
binding.setVariable("a", "apple")
testCase.setBinding(binding)
testCase.call()
}
testMeasurement()
I could reproduce the problem you mention by following code. But i am not sure if this is the correct way to use Binding. GroovyDocs says they are to be used with scripts. Could you point me to documentation which suggests such usage of Binding with Closures.
class TestBinding extends GroovyTestCase {
void testMeasurement() {
prepareData(someClosure)
}
def someClosure = {
assertEquals("apple", a)
}
void prepareData(testCase) {
def binding = new Binding()
binding.setVariable("a", "apple")
//this.setBinding(binding)
testCase.setBinding(binding)
testCase.call()
}
}
This was answered on groovy mailing list:
In a script, def foo will create a local variable, not a property
(private field + getter/setter).
Think of a script a bit like if it's the body of a run() or main() method.
That's where and how you can define local variables.

Resources