I am trying to create a test class with an inner class but I am getting an error "companion object is not allowed here". What is supposedly be the problem? Please see my sample code snippet below. And also i am using JUnit4 for this development.
#RunWith(Enclosed::class)
class OuterUnitTest{
#RunWith(Parameterized::class)
inner class Inner1UnitTest {
#Test
fun testInner1(){
// test code here
}
companion object{
#JvmStatic
#Parameterized.Parameters
fun testData() : Any? {
return something
}
}
}
}
Related
I Created Shadow in Kotlin and My Real Code is also in Kotlin. I created companion object for which i created shadow --->
Shadow Class --->
#Implements(Test::class)
class TestShadow {
companion object {
#Implementation
#Synchronized
#JvmStatic
fun test(variable: String?): Int {
val value = 9
return 5
}
}
And My Main Class --->
class Test {
companion object {
#Synchronized
#JvmStatic
fun test(variable: String?): Int {
return 8
}
}
and I m calling this as Test.test in my test Kotlin File. Everytime real method is called
I am learning kotlin in android studio, and I must be making a syntax error.
I have the class:
abstract class RoomDB : RoomDatabase() {
fun getInstance(context:Context) : RoomDB {
// either finds an existing database or makes a new one
// returns the database
}
}
Later I am writing the following code:
import android.example.app.Database.RoomDB
class MainActivity : AppCompatActivity() {
lateinit var database:RoomDB
override fun onCreate(savedInstanceState: Bundle?) {
database = RoomDB.getInstance(this)
}
}
Autocomplete suggests the getInstance method; however, when I write this code, I get an unresolved reference error to this method. Any idea why?
At the suggestion of Slaw I put the method declaration inside of a companion object which fixed the compile errors. I do not understand why that fixed this.
abstract class RoomDB : RoomDatabase() {
companion object {
fun getInstance(context:Context) : RoomDB {
// either finds an existing database or makes a new one
// returns the database
}
}
}
I´m developing an app using Hilt, all works fine but when I try to run some Espresso test on a device running on below Android P I have encountered an issue.
The problem comes when I try to mock (using Mockk) the ViewModel so I can unit test my Fragment. When the Fragment will try to instanciate te ViewModel I got a NullPointerException when the ViewModel is being created. The NPE is thrown on the method setTagIfAbsent. The problem is that this method is package private as you can see on ViewModel source code, so it can not be mocked on Android < P.
I have tried by using the Kotlin All-Open plugin, it has helped on mocking the ViewModel and stubing it public methods. I try to stub the setTagIfAbsent by using the mockk private stubbing, like this:
every{
myViewModelMock["setTagIfAbsent"](any<String>,any())
} answers {secondArg()}
But when setTagIfAbsent is called, the real method is invoked, throwing the NPE because the ViewModel.mBagOfTags is null because the class is a mock.
The rest of the code is the following:
ViewModel:
#OpenForTesting
#HiltViewModel
class MyViewModel #Inject constructor MyViewModel(private val dependency: Dependency): ViewModel(){
//Rest of the code
}
Fragment:
#AndroidEntryPoint
class MyFragment: Fragment(){
private val viewModel: MyViewModel by viewModels()
//Rest of the code
}
Test class:
#HiltAndroidTest
#RunWith(AndroidJUnit4::class)
class MyFragmentTest {
#Bind
#MockK
lateinit var viewModel: MyViewModel
#get:Rule
var hiltRule = HiltAndroidRule(this)
#Before
fun prepareTest(){
MockkAnnotations.init(this)
hiltRule.inject()
launchFragmentInHiltContainer<MyFragment>()
}
#Test
fun testThatWillMakeAViewModelInvokation(){
onView(withId(R.id.button)).perform(click())
//Assume that button will make the ViewModel be called and created by the delegate
//When this happens the NPE is thrown
}
}
The method launchFragmentInHiltContainer comes from here (Hilt sample app).
If you look at the Mockk Android documentation it is said that < Android P the private methods can not be mocked (it is also said for finals, but the OpenClass plugin fix that problem).
Does anyone have an idea of how can I workaround this or how to fix the test?
Thanks in advance.
Instead of mocking setTagIfAbsent you can mock mBagOfTags using reflection on already mocked instance of ViewModel.
setInternalFieldValue(mockedViewModel, "mBagOfTags", HashMap<String, Any>())
fun setInternalFieldValue(target: Any, fieldName: String, value: Any, javaClass: Class<in Any> = target.javaClass) {
try {
val field = javaClass.getDeclaredField(fieldName)
field.isAccessible = true
field.set(target, value)
} catch (exception: NoSuchFieldException) {
val superClass = javaClass.superclass
if (superClass != null) {
setInternalFieldValue(target, fieldName, value, superClass)
} else {
throw RuntimeException("Field $fieldName is not declared in a hierarchy of this class")
}
}
}
If you use mockk(relaxed = true) this problem is solved
I am new to Dagger 2 and I am trying to learn it with Kotlin. Let me explain my Project structure first. I am having a Class name "Info":
class Info constructor(var message: String) {}
I have created a module for this class "InfoModule"
#Module
class InfoModule {
#Provides #Choose("HELLO")
fun sayHello(): Info{
return Info(message = "Hello dagger 2")
}
#Provides #Choose("HI")
fun sayHi(): Info{
return Info(message = "Hi dagger 2")
}
}
I have created a component interface for this module named "MagicBox"
#Component(modules = [InfoModule::class])
interface MagicBox {
fun poke(app: MainActivity)
}
Then in the MainActivity I have injected the two fields for "Info"
class MainActivity : AppCompatActivity() {
var textView: TextView? = null;
#Inject #field:Choose("HELLO") lateinit var infoHello: Info
#Inject #field:Choose("HI") lateinit var infoHi: Info
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
DaggerMagicBox.create().poke(this)
textView = findViewById<TextView>(R.id.textView)
textView!!.text = infoHi.message
}
}
#Qualifier
#MustBeDocumented
#kotlin.annotation.Retention(AnnotationRetention.RUNTIME)
annotation class Choose(val value: String)
As you see above I haved created a #Choose annotation to learn how #Qualifier works. Up to here the code works perfectly and it is really a magic of Dagger :).
PROBLEM STARTS HERE:>>
Then I decided to Inject another field called "car" inside my MainActivity in the same way the "info" field is injected in the MainActivity. To do that first i need a Car class.
class Car constructor(var engine: Engine, var wheels: Wheels) {
fun drive(){
Log.d("Car","Driving")
}
}
Now the car class needs Engine and Wheels. So below are Engine and Wheel classes
Engine Class:
class Engine {
}
Wheel Class:
class Wheels {
}
Then I have created a Module for Car class
#Module
class CarModule {
#Provides
fun provideEngine(): Engine{
return Engine()
}
#Provides
fun provideWheel(): Wheels{
return Wheels()
}
#Provides #Choose("NewCar")
fun provideCar(engine: Engine, wheels: Wheels): Car{
Log.d("NewCar", "Created")
return Car(engine, wheels)
}
}
The component for Car is below
#Component (modules = [CarModule::class])
interface CarComponent {
fun injectCar(mainActivity: MainActivity)
}
Then I have injected the car field in the MainActivity and I have tried to called the "drive" method of Car class.
Now my MainActivity looks like this.
class MainActivity : AppCompatActivity() {
var textView: TextView? = null;
#Inject #field:Choose("HELLO") lateinit var infoHello: Info
#Inject #field:Choose("HI") lateinit var infoHi: Info
#Inject #field:Choose("NewCar") lateinit var car: Car
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
DaggerMagicBox.create().poke(this)
textView = findViewById<TextView>(R.id.textView)
textView!!.text = infoHi.message
DaggerCarComponent.create().injectCar(this)
car.drive()
}
}
#Qualifier
#MustBeDocumented
#kotlin.annotation.Retention(AnnotationRetention.RUNTIME)
annotation class Choose(val value: String)
The Logcat Error after MakeProject:
error: [Dagger/MissingBinding] #de.test.testtheapp.Choose("HELLO") de.test.testtheapp.Api.Info cannot be provided without an #Provides-annotated method.
public abstract interface CarComponent {
^
#de.test.testtheapp.Choose("HELLO") de.test.testtheapp.Api.Info is injected at
de.test.testtheapp.MainActivity.infoHello
de.test.testtheapp.MainActivity is injected at
de.test.testtheapp.Components.CarComponent.injectCar(de.test.testtheapp.MainActivity)
What i really find strange is that although the "info" field injection was working prefectly before, why after adding car field injection, the logcat is now showing error about info field injection or Info class. I know its also saying something about "CarComponent". Now nothing is working. Even the "DaggerMagicBox" is unresolved. I am clueless about the error and I am stuck on this since two days. My knowledge about dagger is very limited that I don't know what is the solution. I will be very thankful if some give me a clue. I am using Android Studio 3.5.1 and Dagger version 2.21
You are trying to use CarComponent to inject dependencies of MainActivity:
DaggerCarComponent.create().injectCar(this)
But your CarComponent doesn't have a way to provide Info:
#Inject #field:Choose("HELLO") lateinit var infoHello: Info
Because the provider method is defined in InfoModule and CarComponent doesn't have it in its modules list.
You are using two components to inject the dependencies of MainActivity:
DaggerMagicBox.create().poke(this)
...
DaggerCarComponent.create().injectCar(this)
You should only use one.
Either add the CarModule to the list of modules of MagicBox and remove DaggerCarComponent.create().injectCar(this).
Or add the InfoModule to the list of modules of CarComponent and remove DaggerMagicBox.create().poke(this)
I would like to write some unit tests for my custom Application class but my tests fail immediately after I try to create Application object with this error:
java.lang.RuntimeException: Stub!
at android.content.Context.<init>(Context.java:20)
at android.content.ContextWrapper.<init>(ContextWrapper.java:21)
at android.app.Application.<init>(Application.java:36)
...
This is my Application class:
class MainApplication : Application() {
#Inject lateinit var statLogger: StatLogger
override fun onCreate() {
super.onCreate()
inject()
setupStatLogger()
}
private fun inject() {
...
}
private fun configStatLogger() {
statLogger.config()
}
}
And this is my test:
class MainApplicationTest {
val app = MainApplication().apply {
statLogger = mock()
}
#Test
fun `logger is configured`() {
app.onCreate()
verify(app.statLogger).config()
}
}
You can modify your application class to something similar to MVP and/or test the testable components of your Application class in isolation.
You are injecting your StatLogger and the checking the config on it. You can get a copy of your StatLogger under test and then check the config outside of the Application class too.