56

We are currently working with java with kotlin project, slowly migrating the whole code to the latter.

Is it possible to mock static methods like Uri.parse() using Mockk?

How would the sample code look like?

Andrzej Sawoniewicz
  • 1,541
  • 2
  • 16
  • 18

7 Answers7

107

After mockk 1.8.1:

Mockk version 1.8.1 deprecated the solution below. After that version you should do:

@Before
fun mockAllUriInteractions() {
    mockkStatic(Uri::class)
    val uriMock = mockk<Uri>() 
    every { Uri.parse("test/path") } returns uriMock
}

mockkStatic will be cleared everytime it's called, so you don't need to unmock it before using it again.

However, if that static namespace will be shared across tests it will share the mocked behavior. To avoid it, make sure to unmockkStatic after your suite is done.


DEPRECATED:

If you need that mocked behaviour to always be there, not only in a single test case, you can mock it using @Before and @After:

@Before
fun mockAllUriInteractions() {
    staticMockk<Uri>().mock()
    every { Uri.parse("http://test/path") } returns Uri("http", "test", "path")    //This line can also be in any @Test case
}

@After
fun unmockAllUriInteractions() {
    staticMockk<Uri>().unmock()
}

This way, if you expect more pieces of your class to use the Uri class, you may mock it in a single place, instead of polluting your code with .use everywhere.

LeoColman
  • 6,950
  • 7
  • 34
  • 63
  • Your response ("After mockk 1.8.1") is more accurate than the chosen response. Thanks! – Luan Barbosa Dec 28 '18 at 08:56
  • 2
    The accepted answer was written before mockk 1.8.1, that's why this became more accurate – LeoColman Dec 28 '18 at 16:09
  • 1
    This is an excellent solution to mock the `Crashlytics.log` and similar functions called from Java libraries when writing local unit tests! – AdamHurwitz Sep 13 '19 at 04:32
  • 5
    @LeoColman how is your solution supposed to work when i immediately get an compile error on `Uri("http", "test", "path")` with messages "Cannot create an instance of abstract class" and "Too many arguments for public/*package*/ constructor Uri() defined in android.net.Uri"? I'm using mockk 1.10.0 – JU5T1C3 Jul 21 '20 at 13:55
  • The latest Uri is missing `Uri("http", "test", "path")` signature. – prashantwosti Aug 17 '20 at 02:45
  • 1
    @prashantwosti Just use val uriMock = mockk() – jiahao Dec 07 '20 at 05:52
  • how using that mockk will solve the issue of the `Uri` method? – desgraci May 03 '21 at 11:45
  • Uri constructor is private. How do you make this work? Can you please help? – Curtes Malteser May 04 '21 at 09:43
  • 2
    I don't understand how this answer is 73 upvotes, while using Uri() constructor which is A) private and B) the class is abstract... – TootsieRockNRoll Nov 12 '21 at 01:26
  • @TootsieRockNRoll Because the URI constructor isn't important to he answer. And having that Uri as extension function is something likely to happen. The important part of the answer is how to use mockk to mock a static method, and it's clear in that sense. – LeoColman Nov 12 '21 at 02:10
  • Beware in some cases after 1.8.1 you still need to call `unmockkStatic()`. See Tomas Rohovsky answer here: https://stackoverflow.com/a/66479439/674648 – Delblanco Dec 06 '21 at 22:48
  • To help people with the Uri constructor, the answer by @LeoColman can be improved in this way: ` mockkStatic(Uri::class) val uriMock = mockk() every { Uri.parse("http://test/path") } returns uriMock ` – Irving Dev Feb 24 '23 at 18:43
23

Beware

If you call mockkSatic() without a block, do not forget to call unmockkStatic() after the mocked method is called. The method is not unmocked automatically and you will still get the mocked value even in different test classes which do not call mockkStatic(), but use the static method.

Another option is to execute the mocked method inside a block, then it will be automatically unmocked:

mockkStatic(Uri::class) {
    every { Uri.parse("http://test/path") } returns Uri("http", "test", "path")
    val uri = Uri.parse("http://test/path")
}

Tomas Rohovsky
  • 231
  • 2
  • 3
  • 1
    This should be added to the accepted answer. Just found the source of our flaky tests after searching for 8 hours! Thanks Tomas! – Delblanco Dec 06 '21 at 22:50
18

MockK allows mocking static Java methods. The main purpose for it is mocking of Kotlin extension functions, so it is not as powerful as PowerMock, but still does it's job even for Java static methods.

The syntax would be following:

staticMockk<Uri>().use {
    every { Uri.parse("http://test/path") } returns Uri("http", "test", "path")

    assertEquals(Uri("http", "test", "path"), Uri.parse("http://test/path"))

    verify { Uri.parse("http://test/path") }  
}

More details here: http://mockk.io/#extension-functions

oleksiyp
  • 2,659
  • 19
  • 15
14

Additionally to the accepted answer:

You can't create an Uri like that, you gonna have to mock the Uri instance as well. Something like:

private val mockUri = mockk<Uri>()

@Before
fun mockAllUriInteractions() {
    mockkStatic(Uri::class)
    every { Uri.parse("http://test/path") } returns mockUri
    // or just every { Uri.parse("http://test/path") } returns mockk<Uri>()
}
Danilo Lemes
  • 2,342
  • 1
  • 14
  • 16
  • This should be the accepted answer—you cannot construct Uri as described in the other answers because the constructor is private. `mockUri = mockk()` is perfect, thank you. – Tina Jun 12 '21 at 00:27
3

If we are going to mock static, like: mockkStatic(Klass::class)

then we definitely have to unmock it, like: unmockkStatic(Klass::class)

I would suggest to unmock it in the method annotated @After.

A full example would be:

class SomeTest {
  private late var viewMode: SomeViewModel

  @Before
  fun setUp() {
    viewMode = SomeViewModel()
    mockkStatic(OurClassWithStaticMethods::class)       
  }

  @After
  fun tearDown() {
    unmockkStatic(OurClassWithStaticMethods::class)
  }

  @Test
  fun `Check that static method get() in the class OurClassWithStaticMethods was called`() {
    //Given
    every { OurClassWithStaticMethods.get<Any>(any()) } returns "dummyString"

    //When
    viewModel.doSomethingWhereStaticMethodIsCalled()

    //Then
    verify(exactly = 1) { 
       OurClassWithStaticMethods.get<Any>(any()) 
    }
  }
}

This example is written with the mocking library "Mockk" v.1.12.0

neronovs
  • 247
  • 3
  • 6
0

As mentioned in multiple answers above, you'll have to ensure to invoke the unmockkStatic - otherwise you'll end up with flaky tests (since the mocked object/function will be available across test classes.

In my scenario - I had a module-wide extension function in kotlin and to mock that I used a companion object like below:

class SampleTest {

companion object {
    @BeforeAll
    @JvmStatic
    fun setup() {
        mockkStatic("packagename.filenameKt")
    }

    @AfterAll
    @JvmStatic
    fun teardown() {
        unmockkStatic("packagename.filenameKt")
    }
}}
Naren
  • 797
  • 13
  • 12
0
// Add @RunWith(RobolectricTestRunner::class) on top of 
// your class since it provides access to Android framework APIs.

// Test case written inside the `mockkStatic` method to 
// verify the behavior of a method that involves a static method call in Kotlin
        
            @Test
            fun `my test case`() = runBlocking {
                // Mocking the Log class
                mockkStatic(Log::class) {
                    // Test case to verify the behavior of a method 
                    // that involves a log method call
                }
            }




// OR just use `mockkObject(Log)` without any block