Maybe this is very basic question but could not find anything online.
I have created a object class in kotlin contains few methods. I am calling those from ViewModel and I have written junit test case for ViewModel where object class instance is mocked, fine so far.
Now, I want to write junit for my object class separately as well even though from ViewModel verify() calls are working fine.
Some code snippet from my project
object InfoHelper {
fun method(param: Xyz): Boolean {
return when(param) {
Result.OK -> true
else -> false
}
}
}
Unit testing Kotlin object classes:
The same as testing a Java static method and class. The class under test and it's methods can be tested directly without initialisation.
Using your class as a loose example..
object InfoHelper {
fun method(param: String): Boolean {
return when(param) {
"my string" -> true
else -> false
}
}
}
The test:
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Test
class InfoHelperTest {
#Test
fun `some test returns true`() {
assertTrue(InfoHelper.method("my string"))
}
#Test
fun `some test returns false`() {
assertFalse(InfoHelper.method("not my string"))
}
}
Related
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
I have a challenge with a class which I want to test but inside the class other objects will be created.
This simple example shows the issue.
class A {
val b: B
init() {
b = B()
}
}
It's just an example and I know that dependency injection would help. But in real life it's a very complex class which can not be changed easily.
My idea was to use mockkConstructor. But it does not the trick.
fun `test construction`() {
mockkConstructor(B::class)
every { anyConstructed<B>() } returns mockk<B>()
val a = A()
}
Unfortunately, it does not compile. Error: Missing mocked calls inside every { ... } block: make sure the object inside the block is a mock
I tried it this way
fun `test construction`() {
mockkConstructor(B::class)
every { A() } returns mockk<B>()
val a = A()
}
But this way it calls the real constructor of A and also the real constructor of B in the init method of A.
Does anybody know if it's possible to solve it this way or similar?
So I made a test for these example classes:
class A {
val b: B
init {
b = B()
}
}
class B {
fun getA(): String {
return "B"
}
}
Test:
#Test
fun test() {
mockkConstructor(A::class)
val b = mockk<B>()
every { anyConstructed<A>().b } returns b
every { b.getA() } returns "MOCKK"
val a = A()
Assert.assertEquals("MOCKK", a.b.getA())
}
Your attempt to mock B() to return a mock of B is completely redundant after calling mockkConstructor(B::class) which already makes the constructor of B return a prototype mock denoted by anyConstructed<B>().
This means, you only need anyConstructed<B>() to specify the behaviour of calls to that mock, e.g.
every { anyConstructed<B>().someFunctionCall() } returns 1
See MockK documentation: Constructor mocks for more details.
I have written test cases for my view model. Which when I run individually or when I run the Test class. They get executed successfully. But when I run the complete androidTest package, I get this Exception
io.mockk.MockKException
Here is the code that runs successfully in isolation.
#RunWith(AndroidJUnit4::class)
class MyViewModelTest{
#Test
fun test_one(){
getInstrumentation().runOnMainSync(Runnable {
val context = ApplicationProvider.getApplicationContext<Context>()
mockkStatic(MyManager::class)
val myInterface = mockk<MyInterface>()
every { MyManager.getCommunicator() } returns myInterface
every { myInterface.context } returns context
every { myInterface.getLongFromGTM(any()) } returns 0
val viewModel = MyViewModel(context as Application)
viewModel.model = MyDataModel()
viewModel.model.isRepeatEligible = true
val res = viewModel.isRepeatEligible()
Truth.assertThat(res).isTrue()
})
}
}
This is the error I am getting while running entire androidTest package:
Here are the detailed used classes
1 .) MyManager.java
public class MyManager {
private static MyInterface myCommunicator;
public static MyInterface getCommunicator() {
if (myCommunicator == null) {
synchronized (MyManager.class) {
if (myCommunicator == null) {
Class<?> cls = Class.forName("mypackage.communicator.MyCommunicator");
myCommunicator = (MyInterface) cls.newInstance();
}
}
}
return myCommunicator;
}
}
2.) MyViewModel.kt
class MyViewModel(application: Application) : BaseViewModel(application) {
var model = MyDataModel()
private val timerDelay: Long by lazy {
myCommunicator.getLongFromGTM("key_p2m_timer_delay")
}
val timerDuration: Long by lazy {
myCommunicator.getLongFromGTM("key_p2m_timer_duration")
}
fun isRepeatEligible(): Boolean {
model.apply {
return isRepeatEligible && !isLinkBased && !isAlreadyPresent
}
}
Mocking something with MockK is not contrained to just one function. Specifically, when you mock an object with mockkStatic, the object will from then on be a mock until it is unmocked using unmockkStatic or unmockkAll.
In your case, I guess the problem arises due to the static mocking of MyManager that lets subsequent tests fail, because they do not expect the object to be mocked.
This could be solved with an "after" function (e.g. using JUnit4, a function annotated with #After) that calls unmockAll.
Alternatively, if you want to make sure that the object is only mocked locally, you can use a variant of mockkStatic that accepts a block that is the only place where the object is mocked like this:
mockkStatic(MyManager::class) {
// inside this block, MyManager is mocked
}
// MyManager is automatically unmocked after the block
Update
As mentioned in your comment, you do not call MyManager.getCommunicator() directly in MyViewModel, but via an extension property
val myCommunicator : MyInterface = MyManager.getCommunicator()
This may cause your test setup to be still in place after your test, even when you unmock MyManager, because the property myCommunicator will keep its value - the mocked interface.
This can be solved by changing your property to not be initialized with the value of MyManager.getCommunicator(), but instead you should define a getter that calls MyManager.getCommunicator():
val myCommunicator: MyInterface get() = MyManager.getCommunicator()
This way, you do always get the current value of MyManager.getCommunicator() and not the value that was set once on initialization.
See https://kotlinlang.org/docs/properties.html#getters-and-setters for details on property getters.
I can't change project code, I can only write tests.
I have a function for testing:
fun funToBeTested() {
ClasWithWorkManager().veryVeryBadFunc(TAG)
// a lot of code to be tested below
...
}
and class with WorkManager:
class ClasWithWorkManager() {
companion object {
#JvmStatic
fun veryVeryBadFunc(tag: String) {
WorkManager.getInstance().cancelAllWorkByTag(tag)
WorkManager.getInstance().pruneWork()
}
}
}
There are no Android class references in the rest of this func.
Can I somehow cover with Unit test all funToBeTested() except of its first line?
Right now I have obvious WorkManager is not initialized properly. problem
I'm running UI testing on Android devices using Appium. We recently migrated to JUnit5 and I'm attempting to utilize the #BeforeAll class to make sure the app is in a good state before we continue to the next class.
Currently, the tooltip in Android studio is indicating that the function is never used. In the log I'm seeing a junitException saying that the method must be static. I haven't implemented #TestInstance yet, I'd like to be able to use beforeAll without it for now. I'm just confused why it isn't working since my #beforeEach and #afterEach are both working. The error and code are below.
org.junit.platform.commons.JUnitException: #BeforeAll method 'public final void com.bypass.automation.BaseTest.healthcheck()' must be static unless the test class is annotated with #TestInstance(Lifecycle.PER_CLASS).
open class BaseTest {
lateinit var driver: AndroidDriver<MobileElement>
private val capabilities = DesiredCapabilities().apply {
setCapability(APPIUM_VERSION, "1.19.1")
setCapability(PLATFORM_NAME, "Android")
setCapability(DEVICE_NAME, "Android")
setCapability("appPackage", "com.ourpackage")
setCapability("appActivity", "com.ourpackage.PassthroughHomeActivity")
setCapability("automationName", "uiautomator2")
setCapability("skipDeviceInitialization", true)
setCapability("noReset", true)
setCapability("full-reset", false)
setCapability("enableMultiWindows", false)
setCapability("unlockType", "pin")
setCapability("unlockKey", "0000")
setCapability("newCommandTimeout", "120")
}
#BeforeAll
fun healthcheck() {
val currentActivity = driver.currentActivity()
println("Current activity is $currentActivity")
if (currentActivity.contains("StationSecurePayActivity")) {
println("Exiting Station Pay")
CreditCardEntryView(driver).clickBackButton()
}
when {
currentActivity.contains("kiosk") -> {
Thread.sleep(2000)
println("Exiting Kiosk")
KioskView(driver).exitKiosk()
println("Logging out")
LogInProviderUtil(driver).logOut()
}
currentActivity != ".LoginActivity" -> {
println("Logging out")
LogInProviderUtil(driver).logOut()
}
currentActivity.contains(".LoginActivity") -> {
println("Session was properly logged out. No action taken.")
}
}
}
#BeforeEach
fun setup() {
driver = AndroidDriver(URL("http://127.0.0.1:4750/wd/hub"), capabilities)
driver.manage()?.timeouts()?.implicitlyWait(30, SECONDS)
if (LogInProviderUtil(driver).isLoggedIn()){
LogInProviderUtil(driver).logOut()
}
}
#AfterEach
fun teardown() {
if (LogInProviderUtil(driver).isLoggedIn()){
LogInProviderUtil(driver).logOut()
driver.quit()
}
else {
driver.quit()
}
}
}
It will work. I believe that any method annotated with #BeforeAll must be static (unless the "per-class" test instance lifecycle is used). So it sounds to me like you should switch to that by adding this annotation to your test class: #TestInstance(Lifecycle.PER_CLASS)
Also, it is usual practice to make your setup and teardown methods public. Also, I recommend use of Selenium-Jupiter framework (https://github.com/bonigarcia/selenium-jupiter/blob/master/README.md#appium) . Good luck.
If you want to have an initialization block you may put it simply into
init{} method. And you don't have to annotate it.