I'm doing my first deep dive into unit testing with Mockito, so please bear with me. I'm working on this test:
class PasswordStateManagerTest {
private lateinit var passwordStateManager: PasswordStateManager
#MockK
private lateinit var mockContext: Context
#MockK
private lateinit var mockSharedPreferences: SharedPreferences
#Before
fun setup() {
MockKAnnotations.init(this, true)
every{ mockContext.getApplicationSharedPreferences() } returns mockSharedPreferences
// this is the line that won't compile
Mockito.when(mockSharedPreferences.getBoolean("save_password", false)
)
.thenReturn(true)
passwordStateManager = PasswordStateManager(mockSharedPreferences)
}
}
The when.thenReturn line won't compile. It is expecting an open bracket { character where I am trying to execute on .thenReturn. As I read the docs, there is no place for an open bracket in this statement, so I must be off the rails.
Here is the part of the init method of the class being tested, which is what creates the need for the when-thenReturn line in the test:
init {
willSavePassword = prefs.getBoolean("save_password", false)
}
Thanks for any help (and patience while I get up to speed!).
This is because when is a reserved keyword in Kotlin, so the compiler is interpreting this as the beginning of a when statement. For example:
when (value) {
"value1" -> // do thing
}
To fix this, you can either escape the method name with backticks:
Mockito.`when`(mockSharedPreferences.getBoolean("save_password", false)).thenReturn(true);
Or, since you're using MockK anyway, just switch to another every:
every { mockSharedPreferences.getBoolean("save_password", false) } returns true
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'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.
I'm writing a unit test. Below is my code. The architecture is MVVM using Dagger2. I'm calling the login function residing in the LoginViewModel, which is notifying the getLoginState function. The error I'm getting is:
Error:
io.mockk.MockKException: no answer found for: Observer(#8).onChanged(Success(data=))
at io.mockk.impl.stub.MockKStub.defaultAnswer(MockKStub.kt:90)
LoginViewModelClass:
fun logIn(phone: String, phoneCode: String) {
loginState.value = Outcome.success("")
}
fun getLoginState(): LiveData<Outcome<String>> = loginState
LoginViewModelTest class:
#RelaxedMockK
var SUT: LoginViewModel? = null
#Mock
var loginInteractor: LoginInteractor? = null
#Mock
var textValidator: TextValidator? = null
#Mock
var textProvider: TextProvider? = null
#Mock
var blinkUserPreferences: BlinkUserPreferences? = null
#get:Rule
var rule: TestRule = InstantTaskExecutorRule()
#RelaxedMockK
var mockObserver: Observer<Outcome<String>>? = null
#Before
fun setUp() {
MockKAnnotations.init(this, relaxUnitFun = true)
SUT = spyk(
LoginViewModel(
mockk<LoginInteractor>(),
mockk<TextValidator>(relaxed = true),
mockk<TextProvider>(),
mockk<BlinkUserPreferences>()))
mockObserver = mockk<Observer<Outcome<String>>>()
SUT!!.getLoginState().observeForever(mockObserver!!)
}
#Test
fun logIn() {
//Arrange
every {SUT!!.getLoginState().value} returns Outcome.success("")
//Act
SUT!!.logIn("89989676","89998")
//Assert
verify() { mockObserver!!.onChanged(Outcome.success("abc")) }
}
Question:
In verification, why onChanged method is not being called, or what does it mean that no answer found for Observer().onChanged, how can I notify my onChanged method so I can verify it?
After watching this: https://mockk.io/#answers. It says
specify that the matched call answers with a code block scoped with
answer scope
I just posted this:
every { mockObserver!!.onChanged(any()) } answers {}
in the following test function and it worked.
#Test
fun logIn() {
//Arrange
every { mockObserver!!.onChanged(any()) } answers {}
every {SUT!!.getLoginState().value} returns Outcome.success("abc")
//Act
SUT!!.logIn("89989676","89998")
//Assert
verify() { mockObserver!!.onChanged(Outcome.success("abc")) }
}
According to my understanding, if you mockk a function, and you want to use its particular function you must use the every expression to tell framework that it will answer, because framework needs to know that it needs to answer something.
And if you want that all behaviour functions should also be added with mock with their implementation then you must spyk your class so that it gets the behaviour as well and then you can easily use the function without using expression every.
Please note that every expression is used for many cases like to get a mocked result out of that function, or just need to tell the framework that this function should answers this.
Please correct me through comments if I'm wrong, Ill update it.
I'm trying my hand at TDD with an Android app. I'm writing it in Kotlin, and because of that I've turned to MockK for testing, but there's one thing (for now) that I haven't been able to find out how to do: test a suspend call.
I wrote a test for a LiveData value in a ViewModel, and made it work. However, when I added coroutines to the mix, I started getting the "Method getMainLooper not mocked" message.
Here's my code:
ToDoListViewModelTest.kt
class ToDoListViewModelTest {
#get:Rule
var instantExecutorRule = InstantTaskExecutorRule()
#MockK
private lateinit var toDoListLiveDataObserver: Observer<List<ToDoItem>>
#MockK
private lateinit var getToDoItemsUseCase: GetToDoItemsUseCase
#Before
fun setUp() {
MockKAnnotations.init(this)
every { toDoListLiveDataObserver.onChanged(any()) } answers { nothing }
}
#Test
fun toDoList_listItems_noItems() = runBlocking {
coEvery { getToDoItemsUseCase() } coAnswers { emptyList<ToDoItem>() }
val toDoListViewModel = ToDoListViewModel(getToDoItemsUseCase)
toDoListViewModel.toDoItemList.observeForever(toDoListLiveDataObserver)
toDoListViewModel.updateItemList()
assertEquals(0, toDoListViewModel.toDoItemList.value?.size)
}
}
ToDoListViewModel.kt
class ToDoListViewModel(private val getToDoItemsUseCase: GetToDoItemsUseCase) : ViewModel() {
private val _toDoItemList: MutableLiveData<List<ToDoItem>> = MutableLiveData()
val toDoItemList : LiveData<List<ToDoItem>> = _toDoItemList
fun updateItemList() {
viewModelScope.launch(Dispatchers.IO) {
_toDoItemList.value = getToDoItemsUseCase()
}
}
}
GetToDoItemsUseCase.kt
class GetToDoItemsUseCase {
suspend operator fun invoke(): List<ToDoItem> {
return listOf()
}
}
Things I've tried:
Adding "#RunWith(BlockJUnit4ClassRunner::class)": No change
Adding "testOptions { unitTests.returnDefaultValues = true }" to the Gradle file: The Looper error goes away, but the value coming from the LiveData is null, instead of the empty list specified in the "coEvery" call.
Calling "Dispatchers.setMain(newSingleThreadContext("UI Thread"))": Same as previous case, getting null from LiveData.
I'm not very experienced with testing, and I've run out of options. I feel I definitely need some help from the community ;)
Also, if for some reason my setup isn't the right one (should use something other than MockK, or some other testing framework...), please comment on that too. I still have much to learn regarding this.
Use postValue _toDoItemList.postValue(getToDoItemsUseCase())
Based on the documentation:
setValue():
Sets the value. If there are active observers, the value will be
dispatched to them. This method must be called from the main thread.
postValue():
Posts a task to a main thread to set the given value. If you called
this method multiple times before a main thread executed a posted
task, only the last value would be dispatched.
I'm playing with lint rules.
All my ResourceXmlDetector run without problems and pass all the tests. But Detector(), SourceCodeScanner are failing because they return 0 warnings/errors, and the reason is visitMethodCall not being called thus context.report wont either.
My code is similar to android lint-checks, for instance CipherGetInstanceDetector, but I can't find my mistake.
#Suppress("UnstableApiUsage")
class MySourceDetector : Detector(), SourceCodeScanner {
override fun getApplicableMethodNames() = listOf("...")
override fun visitMethodCall(context: JavaContext, node: UCallExpression, method: PsiMethod) {
if (context.evaluator.isMemberInClass(method, "...")) {
...
reportUsage(context, node)
}
}
private fun reportUsage(context: JavaContext, node: UCallExpression) {
context.report(
issue = ISSUE,
scope = node,
location = context.getCallLocation(
call = node,
includeReceiver = true,
includeArguments = true
),
message = ISSUE.getExplanation(TextFormat.RAW)
)
}
companion object {
#JvmField
val ISSUE = Issue.create(...Scope.JAVA_FILE_SCOPE)
}
}
The only methods stopping in break points are Issue.create and getApplicableMethodNames(). What's missing?
According UElementVisitor#DelegatingPsiVisitor.visitMethodCallExpression in source code, I found that some java or kotlin method can not be recognized as "Method":val function = node.resolve() is null.
I didn't think this was important but my test rule was:
TestLintTask.lint()
.files(TestFiles.kotlin("Test.kt", input).indented())
.issues(ISSUE)
.run()
.expectWarningCount(1)
.expect(output)
And replacing kotlin("Test.kt", input) with kotlin(input) made it work...