mockk not working while executing entire android test package - android

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.

Related

Mock a constructor and return a mocked object instead of real object with mockk

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.

Using capture and mocks to unit test a class

I am trying to unit test the following class:
class UserProfileDetailsAnalyticUseCaseImp #Inject constructor(private val analyticsProvider: AnalyticsProvider) : UserProfileDetailsAnalyticUseCase {
override fun execute(cdsCustomer: CDSCustomer) {
with(analyticsProvider) {
log(AnalyticEvent.UserId(cdsCustomer.id.toString()))
log(AnalyticEvent.UserEmail(cdsCustomer.email))
}
}
}
And this is my unit test:
class UserProfileDetailsAnalyticUseCaseImpTest {
private lateinit var userProfileDetailsAnalyticUseCaseImp: UserProfileDetailsAnalyticUseCaseImp
private val analyticsProviders: AnalyticsProvider = mock()
#Before
fun setUp() {
userProfileDetailsAnalyticUseCaseImp = UserProfileDetailsAnalyticUseCaseImp(analyticsProviders)
}
#Test
fun `should send analytic event`() {
// Arrange
val cdsCustomer = CDSCustomer(
id = Random.nextInt(0, 100000),
email = UUID.randomUUID().toString())
val userIdCapture= argumentCaptor<AnalyticEvent.UserId>()
val userEmailCapture= argumentCaptor<AnalyticEvent.UserEmail>()
// Act
userProfileDetailsAnalyticUseCaseImp.execute(cdsCustomer)
// Assert
verify(analyticsProviders, atLeastOnce()).log(userIdCapture.capture())
verify(analyticsProviders, atLeastOnce()).log(userEmailCapture.capture())
assertThat(userIdCapture.firstValue.userId).isEqualTo(cdsCustomer.id.toString())
assertThat(userEmailCapture.firstValue.email).isEqualTo(cdsCustomer.email)
}
}
The error I get is the following:
AnalyticEvent$UserId cannot be cast to AnalyticEvent$UserEmail
I am suspecting that because class under test is creating a new object for each log method they will not be the same for the verified methods in the unit test
i.e log(AnalyticEvent.UserId(cdsCustomer.id.toString()))
As a new AnaltyicEvent.UserId will be created and just for the same AnalyticProvider mock
Many thanks for any suggetions
In the documentation of ArgumentCaptor we can read that:
This utility class doesn't do any type checks. The generic
signatures are only there to avoid casting in your code.
Moreover CapturingMatcher which is used for collecting captured arguments has a method which matches all objects:
public boolean matches(Object argument) {
return true;
}
It means that it is normal behaviour and even when we specify concrete type of captor it will record all arguments passed.
Of course all these arguments have to inherit from the same base class because in other case capture method will cause compilation error.
So, both your captors record two arguments.
To fix class cast exception for your test you can assert secondValue for email.
assertThat(userEmailCapture.secondValue.email).isEqualTo(cdsCustomer.email)
You can also stop using argument captors and simply verify invocations of log method.
verify(analyticsProviders).log(AnalyticEvent.UserId(cdsCustomer.id.toString()))
verify(analyticsProviders).log(AnalyticEvent.UserEmail(cdsCustomer.email))

#BeforeAll not functioning as intended in JUnit5

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.

Mockito.when().thenReturn() not used by other methods

I have the following test setup:
class RepositoryTest {
private lateinit var repository: Repository
#Before
fun setup() {
repository = Mockito.mock(Repository::class.java)
Mockito.`when`(repository.getList()).thenReturn(getMockedList())
}
// works
#Test
#Throws(Exception::class)
fun getList() {
val list = repository.getList()
assertNotNull(list)
assertFalse(list.isEmpty())
}
// does not work
#Test
#Throws(Exception::class)
fun getList() {
// getFilteredList is internally using getList()
val list = repository.getFilteredList()
assertNotNull(list)
assertFalse(list.isEmpty())
}
}
So my question is, does the mocking of the return type for getList not work for implicit method calls? And what would be the appropriate way to mock these implicit method calls?
getFilteredList
That's how mocks work.
Your repository is a mock object and does not contain any actual code. By default, for all methods that return a value, a mock will return either null, a primitive/primitive wrapper value, or an empty collection. Since getFilteredList() is not stubbed, you're getting a default return value that does not pass the assertions you have later.
You can make the mock call your actual method with something like
when(repository.getFilteredList()).thenCallRealMethod()

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