Mockito Uri.parse always returns null
Asked Answered
B

12

23

I have this production method:

public boolean onShouldOverrideUrlLoading(String url) {
    boolean isConsumed = false;
    if (url.contains("code=")) {
         Uri uri = Uri.parse(url);
         String authCode = uri.getQueryParameter("code");
         mView.authCodeObtained(authCode);
         isConsumed = true;
    }
    return isConsumed;
}

And I have this Mockito test method:

@Test
public void onShouldOverrideUrlLoadingOnAuthCodeObtained(){

    String code = "someCode";

    boolean isConsumed = mPresenter.onShouldOverrideUrlLoading("http://localhost/?code=" + code);

    verify(mView, times(1)).authCodeObtained(code);
    assertEquals(isConsumed, true);
}

But it seems once the code runs and it reaches Uri.parse(url), I get a null pointer. What am I missing? In production this works perfectly. Only when testing, Uri.parse() returns null.

Thank you!

Bandage answered 16/8, 2015 at 13:36 Comment(4)
The problem is probably in Uri, it probably badly initialize in the test environment. We Uri does not exists in the Android SDK URI does. So it doesn't seem like a shipped class, this could be the issue. Nothing related to mockito.Frenetic
@Brice Thank you. Yes that seemed to be the issue. You should post this as the answer. The Uri class comes from Android. So Mockito has some difficulties working with it that way.Bandage
done :) Hope that helped.Frenetic
@Alon How did you solve the problem exactly? How do I init my test environment correctly in Android?Expectant
F
2

The problem probably lies in Uri class, as the code being tested is static code (Uri.parse(...)), Uri is probably badly initializing in the test environment. Note there's two uri classes in the Android SDK :

I'm not an Android developer, but you may want to check the test environment.

Frenetic answered 23/8, 2015 at 10:30 Comment(2)
How did you solve the problem exactly? How do I init my test environment correctly in Android?Expectant
@user59442 I cannot answer you on this one, I'm not an Android developer. Ask the question's author Alon Minski instead!Frenetic
B
23

Eventually I used PowerMock on top of Mockito to mock the Uri class. Use these dependecies to add it:

'org.powermock:powermock-api-mockito:1.4.12'
'org.powermock:powermock-module-junit4:1.6.2'

Read about it here.

It enables you to mock static methods, private methods, final classes and more. In my test method I used:

PowerMockito.mockStatic(Uri.class);
Uri uri = mock(Uri.class);

PowerMockito.when(Uri.class, "parse", anyString()).thenReturn(uri);

This passed the line in the actual code and returned a Mocked Uri object so the test could move forward. Make sure to add:

@RunWith(PowerMockRunner.class)
@PrepareForTest({Uri.class})

As annotations on top of the test class name.

Using Robolectric to tackle this issue is also a valid technique.

Hope this helps some what.

Bandage answered 8/12, 2015 at 9:29 Comment(0)
D
8

I got around this by using the Roboelectric test runner.

@RunWith(RobolectricGradleTestRunner::class)
@Config(constants = BuildConfig::class, sdk = intArrayOf(19))
class WeekPageViewModelConverterTests {
...
}
Disastrous answered 12/2, 2016 at 0:43 Comment(1)
My equivalent way to write this in Java, to the above Kotlin, is: @RunWith(RobolectricTestRunner.class) public class ExampleUnitTest {Catalyst
H
5

The problem here is the fact that android.jar file that is used to run unit tests does not contain any actual code. This way android.net.Uri will return null by default and using

android {
  // ...
  testOptions { 
    unitTests.returnDefaultValues = true
  }
}

will not solve the issue for this case, since default return value for method still null

The solution for me was to use the Unmock plugin which sole purpose is to unmock some of the classes/packages from android.jar and give the full implementation for those.

In this specific case, simply add the snippet to you build.gradle file

apply plugin: 'de.mobilej.unmock'
 //...
unMock {
    keepStartingWith "android.net.Uri"
}
Hairbrush answered 28/11, 2017 at 10:34 Comment(1)
This worked, but I had to give additional keep rules unMock { keep "android.net.Uri" keepAndRename "java.nio.charset.Charsets" to "xjava.nio.charset.Charsets" keepStartingWith "libcore." }Siderosis
N
3

I solved the problem by running the test suite with "@RunWith(RobolectricTestRunner::class)"

Neighboring answered 23/9, 2019 at 4:33 Comment(2)
when I added this library my test is gonna keep building for several minutes and I can't see the result any moreViglione
finnally it works, for first building in gonna last a long time in my case it was nearly 20 minutes .Thanks for this approach...........Viglione
F
2

The problem probably lies in Uri class, as the code being tested is static code (Uri.parse(...)), Uri is probably badly initializing in the test environment. Note there's two uri classes in the Android SDK :

I'm not an Android developer, but you may want to check the test environment.

Frenetic answered 23/8, 2015 at 10:30 Comment(2)
How did you solve the problem exactly? How do I init my test environment correctly in Android?Expectant
@user59442 I cannot answer you on this one, I'm not an Android developer. Ask the question's author Alon Minski instead!Frenetic
C
2

For anyone else looking, you can mock a Uri using mockito.

In kotlin:

mockkStatic(Uri::class)
every { Uri.parse(any()) } returns mockk()

Here is a link with more info on mocking static methods.

Chui answered 25/4, 2023 at 15:46 Comment(0)
C
1

I had a similar problem doing tests with android.net.Uri class.

I've solved the problem moving the test from the 'test' folder to 'androidTest' and running it in the device. In the device, the Uri class behaves normally.

Chitkara answered 12/1, 2017 at 12:26 Comment(0)
G
1

I was able to use android.net.Uri using AndroidJUnit4 runner as follows

import androidx.core.net.toUri
import androidx.test.ext.junit.runners.AndroidJUnit4
...
@RunWith(AndroidJUnit4::class)
class FileTest {
  @get:Rule(order = 0)
  val tempFolder = TemporaryFolder()
  private lateinit var tempFile: File

  @Before
  fun setup() {
    tempFile = tempFolder.newFile()
  }

  @Test
  fun test_fileUri_not_null() {
    assertTrue(tempFile.exists())
    assertNotNull(tempFile.toUri())
  }
}

Above test is written under tests directory

Georgeanngeorgeanna answered 12/5, 2021 at 18:49 Comment(1)
Getting error for tempFile.toUri() : Cannot inline bytecode built with JVM target 1.8 into bytecode that is being built with JVM target 1.6. Please specify proper '-jvm-target' optionSectorial
H
0

If you use the android.net.Uri class, then you need to run your tests with Robolectric (http://robolectric.org) on top of Mockito

Herzel answered 8/12, 2015 at 9:14 Comment(0)
A
0

The only solution that worked for me was the following, while using powermockito.

PowerMockito.mockStatic(Uri.class);
Uri uri = mock(Uri.class);
when(uri.getLastPathSegment()).thenReturn("path");

when(Uri.parse(anyString())).thenAnswer((Answer<Uri>) invocation -> uri);

In this, we can set custom values to be returned by respective functions of uri object.

Alejandrinaalejandro answered 27/1, 2022 at 19:56 Comment(0)
C
0

The problem is that the android jar that's used during unit test runs doesn't have any real implementations. We can do the following to use a proper implementation of Uri:

  • Add "com.github.bjoernq:unmockplugin:x.x.x" to the classpath in the project's or module's buildScript

  • Add de.mobilej.unmock in the plugin block as an id

  • Add the following task:

    unmock { keep("android.net.Uri") }

  • Run your unit test. Uri methods should start working.

Consistent answered 18/8, 2023 at 23:57 Comment(0)
I
0

I used real Uri with Base64 Image data TestWatcher and worked fine for me...

TestWatcher Implementation :

class TestContentUriRule(
    private val base64File :String = DEFAULT_BASE64_IMAGE,
    private val filename:String = DEFAULT_FILENAME
) : TestWatcher() {

    companion object {
        const val DEFAULT_BASE64_IMAGE = "iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAApgAAAKYB3X3/OAAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAANCSURBVEiJtZZPbBtFFMZ/M7ubXdtdb1xSFyeilBapySVU8h8OoFaooFSqiihIVIpQBKci6KEg9Q6H9kovIHoCIVQJJCKE1ENFjnAgcaSGC6rEnxBwA04Tx43t2FnvDAfjkNibxgHxnWb2e/u992bee7tCa00YFsffekFY+nUzFtjW0LrvjRXrCDIAaPLlW0nHL0SsZtVoaF98mLrx3pdhOqLtYPHChahZcYYO7KvPFxvRl5XPp1sN3adWiD1ZAqD6XYK1b/dvE5IWryTt2udLFedwc1+9kLp+vbbpoDh+6TklxBeAi9TL0taeWpdmZzQDry0AcO+jQ12RyohqqoYoo8RDwJrU+qXkjWtfi8Xxt58BdQuwQs9qC/afLwCw8tnQbqYAPsgxE1S6F3EAIXux2oQFKm0ihMsOF71dHYx+f3NND68ghCu1YIoePPQN1pGRABkJ6Bus96CutRZMydTl+TvuiRW1m3n0eDl0vRPcEysqdXn+jsQPsrHMquGeXEaY4Yk4wxWcY5V/9scqOMOVUFthatyTy8QyqwZ+kDURKoMWxNKr2EeqVKcTNOajqKoBgOE28U4tdQl5p5bwCw7BWquaZSzAPlwjlithJtp3pTImSqQRrb2Z8PHGigD4RZuNX6JYj6wj7O4TFLbCO/Mn/m8R+h6rYSUb3ekokRY6f/YukArN979jcW+V/S8g0eT/N3VN3kTqWbQ428m9/8k0P/1aIhF36PccEl6EhOcAUCrXKZXXWS3XKd2vc/TRBG9O5ELC17MmWubD2nKhUKZa26Ba2+D3P+4/MNCFwg59oWVeYhkzgN/JDR8deKBoD7Y+ljEjGZ0sosXVTvbc6RHirr2reNy1OXd6pJsQ+gqjk8VWFYmHrwBzW/n+uMPFiRwHB2I7ih8ciHFxIkd/3Omk5tCDV1t+2nNu5sxxpDFNx+huNhVT3/zMDz8usXC3ddaHBj1GHj/As08fwTS7Kt1HBTmyN29vdwAw+/wbwLVOJ3uAD1wi/dUH7Qei66PfyuRj4Ik9is+hglfbkbfR3cnZm7chlUWLdwmprtCohX4HUtlOcQjLYCu+fzGJH2QRKvP3UNz8bWk1qMxjGTOMThZ3kvgLI5AzFfo379UAAAAASUVORK5CYII="
        const val DEFAULT_FILENAME = "image.jpg"
    }

    lateinit var file: File
    lateinit var contentUri: Uri

    override fun starting(description: Description?) {
        super.starting(description)
        val context = ApplicationProvider.getApplicationContext<Context>()

        val decodedString: ByteArray = Base64.decode(base64File, Base64.DEFAULT)
        val bitmap = BitmapFactory.decodeByteArray(decodedString, 0, decodedString!!.size)

        try {
            file = File(context.cacheDir, filename)
            bitmap.compress(Bitmap.CompressFormat.JPEG, 100, file.outputStream())
            contentUri = Uri.fromFile(file)
        } catch (e: Exception) {
            e.printStackTrace()
        }
    }

    override fun finished(description: Description?) {
        val context = ApplicationProvider.getApplicationContext<Context>()
        File(context.cacheDir, filename).delete()
        super.finished(description)
    }
}

and usage :

class CashReceiptPageTest : TraderAndroidTest() {

    @get:Rule
    val contentUriRule = TestContentUriRule()

    @Test
    fun sampleTest() {
        // some codes ...
        selectReceiptPicture(uri = contentUriRule.contentUri)
        // some codes ...
    }
}
Igor answered 24/6 at 15:23 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.