22

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!

bric3
  • 40,072
  • 9
  • 91
  • 111
Alon Minski
  • 1,571
  • 2
  • 19
  • 32
  • The problem is probably in Uri, it probably badly initialize in the test environment. We `Uri` does not exists in the Android SDK [`URI`](https://developer.android.com/reference/java/net/URI.html#create(java.lang.String)) does. So it doesn't seem like a shipped class, this could be the issue. Nothing related to mockito. – bric3 Aug 16 '15 at 14:29
  • @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. – Alon Minski Aug 23 '15 at 06:21
  • done :) Hope that helped. – bric3 Aug 23 '15 at 10:32
  • @Alon How did you solve the problem exactly? How do I init my test environment correctly in Android? – Johannes Dec 07 '15 at 15:48

11 Answers11

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.

Alon Minski
  • 1,571
  • 2
  • 19
  • 32
8

I got around this by using the Roboelectric test runner.

@RunWith(RobolectricGradleTestRunner::class)
@Config(constants = BuildConfig::class, sdk = intArrayOf(19))
class WeekPageViewModelConverterTests {
...
}
Brice Pollock
  • 173
  • 1
  • 9
  • 3
    My equivalent way to write this in Java, to the above Kotlin, is: `@RunWith(RobolectricTestRunner.class)` `public class ExampleUnitTest {` – Peter Dietz Apr 09 '18 at 16:08
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"
}
Felipe Conde
  • 2,024
  • 23
  • 26
  • 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." } – harmanjd Sep 20 '19 at 14:42
3

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

prago
  • 5,225
  • 5
  • 25
  • 27
  • when I added this library my test is gonna keep building for several minutes and I can't see the result any more – Mostafa Imani Apr 10 '22 at 06:40
  • finnally it works, for first building in gonna last a long time in my case it was nearly 20 minutes .Thanks for this approach........... – Mostafa Imani Apr 10 '22 at 06:55
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.

bric3
  • 40,072
  • 9
  • 91
  • 111
  • How did you solve the problem exactly? How do I init my test environment correctly in Android? – Johannes Dec 07 '15 at 10:52
  • 1
    @user59442 I cannot answer you on this one, I'm not an _Android developer_. Ask the question's author Alon Minski instead! – bric3 Dec 07 '15 at 13:26
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.

iuri
  • 21
  • 3
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

Kshitij Patil
  • 132
  • 1
  • 9
  • 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' option – Nainal Aug 11 '21 at 11:11
1

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.

Mira_Cole
  • 763
  • 6
  • 13
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

VinceFR
  • 2,551
  • 1
  • 21
  • 27
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.

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.

NSaran
  • 561
  • 3
  • 19