#BeforeAll not functioning as intended in JUnit5 - android

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.

Related

How to mock a class of a library used by the app during instrumented test?

I am working on an Android library and I am writing an instrumented test for it using UI Automator and Mockk.
The library has a class called InstallManager which will install stuff on the device. I want the InstallManager to throw an exception so that I can test if an error notification will be shown.
All I do is finding the Update (Install) button and click on it
val updateButtonComponent = device.findObject(By.text(updateButtonText))
updateButtonComponent.click()
How can I mock/manipulate the InstallManager which is being used by the library during the automated test?
What I tried:
I tried mocking the InstallManager before running the automated test, hoping that UI Automator would magically know that it should use this mocked InstallManager. But (as I thought already) it does not work like that...
private fun breakInstallManager() {
installManager = spyk(InstallManager(mockk(relaxed = true), nonExistentFile))
every { installManager.getString(any()) } returns ""
every { installManager.packageName } returns ""
}
For mocking InstallManager class in android tests, call MockKAnnotations.init method. Then in the test method you can specify the return value
#RelaxedMockK
private lateinit var installManager: InstallManager
#Before
fun setUp() {
MockKAnnotations.init(this)
// …
}
#Test
fun installManagerTest(){
every { installManager.getString(any()) } returns ""
every { installManager.packageName } returns ""
// …
}

Unit test fails for coroutine if "by inject{parametersOf("someValue")}" is present within coroutine

I have a coroutine in my viewModel which runs perfectly fine. When I try to unit test the same, it throws the following error "Could not create instance for [type:Factory,primary_type:..MyService"
I am injecting a service and making an API call which works fine while unit testing. If the API fails, I am retrying the same API call with a new instance of Service with different parameters. This works fine in my application but fails in unit test. Here is the following code:
coroutineScope.launch {
try {
var getResponse = myApi?.getCodeApi()
if (getResponse?.code() == HttpURLConnection.HTTP_UNAUTHORIZED) {
// Retrying with instance of service with a different token
val newMyApiService: MyService? by inject { parametersOf(newToken) }
getResponse = newMyApiService?.getCodeApi()
}
checkResponse(getResponse)
} catch (exception: Exception) {
Timber.e(exception)
}
}
Is there a way to fix this?. I have taken all the required measures like startingKoinApp for the test environment, also included the required Koin modules before starting to test.
A part of unit test looks something like this
whenever(myAPi.getCodeApi()).thenReturn(properResponse)
val errorResponse : Response<DataModel> = mock()
whenever(response.code()).thenReturn(HttpsURLConnection.HTTP_UNAUTHORIZED)
whenever(myAPi.getCodeApi()).thenReturn(errorResponse)
This can be fixed by replacing the line
val newMyApiService: MyService? by inject { parametersOf(newToken) }
with
val newMyApiService: getNewService(newToken)
The new method will be
fun getNewService(newToken: String): MyService? {
return MyService? by inject { parametersOf(newToken) }
}
Now within your unit test, you can mock the method using power mockito
val underTestsSpy = PowerMockito.spy(underTests)
PowerMockito.doReturn(myserviceApi).`when`(underTestsSpy,
"getNewService", newToken)
Through this, you can mock a new service instance that is created within the method being tested

How do I test a Kotlin suspend call in Android with MockK?

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.

How do I test Kotlin coroutines inside a function?

I am creating a library and I'm using Retrofit with a call-adapter that gives me a Deferred<> value.
In a function in my code I call launch {}, and inside that i try-catch the values, and possible exceptions - calling different callbacks for different results.
The resources I've found on testing coroutines are all about testing suspended functions, and runBlocking {} is the solution to everything. Except for me it isn't
I made a quick example
#Mock
val mockListener: DoSomething.Listener = mock()
#Test
fun testSomething() {
val doer = DoSomething(mockListener)
runBlocking {
doer.doIt()
verify(mockListener).listen(any())
}
}
class DoSomething(val listener: Listener) {
interface Listener {
fun listen(s: String)
}
fun doIt() {
launch {
listener.listen(theThing().await())
}
}
private fun theThing(): Deferred<String> {
return async {
delay(5, TimeUnit.SECONDS)
return#async "Wow, a thing"
}
}
}
What I want is for the actually run all functions. The test should take 5 seconds minimum, but it just runs through the code in a couple of millisconds- ie. it doesn't block.
I've tried adding
runBlocking {
launch {
// doer.doIt()
}.joinChildren()
}
And similar practices but I just can't get the test to actually wait for my launch inside of another class to finish before the test is finished.
Placing the verify(...) outside of the runBlocking also makes the test fail, which it should.
Any input, helpers, good practice etc. is appreciated!
You can provide the CoroutineContext explicitly for your doIt() function:
fun doIt(context: CoroutineContext = DefaultDispatcher) {
launch(context) {
listener.listen(theThing().await()
}
}
With this parameter you could easily change the coroutine context - in your test code you use the blocking context:
runBlocking {
doer.doIt(coroutineContext)
}
BTW: You don't need to use launch and async. With launch you are in a suspendable context and you don't need to run theThing() asynchronously. Especially if you invoke await() in the next step:
fun doIt(context: CoroutineContext = DefaultDispatcher) {
launch(context) {
listener.listen(theThing())
}
}
private suspend fun theThing(): String {
delay(5, TimeUnit.SECONDS)
return "Wow, a thing"
}
Best way would be not to swallow Job in your doIt() function as you do now.
Instead of
fun doIt() {
launch {
listener.listen(theThing().await())
}
}
Do
fun doIt() = launch {
listener.listen(theThing().await())
}
That way your function will return a coroutine, which you can wait for:
doIt().join()
Better still is to use async() instead of launch()
Another comment is that doIt() should be actually doItAsync(), as suggested by Kotlin guidelines.

Unit testing coroutines on UI thread

I'm using coroutines to do an asynchronous call on pull to refresh like so:
class DataFragment : Fragment(), SwipeRefreshLayout.OnRefreshListener {
// other functions here
override fun onRefresh() {
loadDataAsync()
}
private fun loadDataAsync() = async(UI) {
swipeRefreshLayout?.isRefreshing = true
progressLayout?.showContent()
val data = async(CommonPool) {
service?.getData() // suspending function
}.await()
when {
data == null -> showError()
data.isEmpty() -> progressLayout?.showEmpty(null, parentActivity?.getString(R.string.no_data), null)
else -> {
dataAdapter?.updateData(data)
dataAdapter?.notifyDataSetChanged()
progressLayout?.showContent()
}
}
swipeRefreshLayout?.isRefreshing = false
}
}
Everything here works fine when I actually put it on a device. My error, empty, and data states are all handled well and the performance is good. However, I'm also trying to unit test it with Spek. My Spek test looks like this:
#RunWith(JUnitPlatform::class)
class DataFragmentTest : Spek({
describe("The DataFragment") {
var uut: DataFragment? = null
beforeEachTest {
uut = DataFragment()
}
// test other functions
describe("when onRefresh") {
beforeEachTest {
uut?.swipeRefreshLayout = mock()
uut?.onRefresh()
}
it("sets swipeRefreshLayout.isRefreshing to true") {
verify(uut?.swipeRefreshLayout)?.isRefreshing = true // says no interaction with mock
}
}
}
}
The test is failing because it says that there was no interaction with the uut?.swipeRefreshLayout mock. After some experimenting, it seems this is because I'm using the UI context via async(UI). If I make it just be a regular async, I can get the test to pass but then the app crashes because I'm modifying views outside of the UI thread.
Any ideas why this might be occurring? Also, if anyone has any better suggestions for doing this which will make it more testable, I'm all ears.
Thanks.
EDIT: Forgot to mention that I also tried wrapping the verify and the uut?.onRefresh() in a runBlocking, but I still had no success.
If you want to make things clean and consider using MVP architecture in the future you should understand that CourutineContext is external dependency, that should be injected via DI, or passed to your presenter. More details on topic.
The answer for your question is simple, you should use only Unconfined CourutineContext for your tests. (more)
To make things simple create an object e.g. Injection with:
package com.example
object Injection {
val uiContext : CourutineContext = UI
val bgContext : CourutineContext = CommonPool
}
and in test package create absolutely the same object but change to:
package com.example
object Injection {
val uiContext : CourutineContext = Unconfined
val bgContext : CourutineContext = Unconfined
}
and inside your class it will be something like:
val data = async(Injection.bgContext) {service?.getData()}.await()

Categories

Resources