Макет статических методов Java с использованием Mockk

В настоящее время мы работаем с java с проектом kotlin, медленно переводя весь код в последний.

Можно ли издеваться над статическими методами, такими как Uri.parse() с помощью Mockk?

Как будет выглядеть пример кода?

7 ответов

Решение

MockK позволяет высмеивать статические методы Java. Основной целью этого является насмешка над функциями расширения Kotlin, поэтому он не такой мощный, как PowerMock, но все же выполняет свою работу даже для статических методов Java.

Синтаксис будет следующим:

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") }  
}

Подробнее здесь: http://mockk.io/

В дополнение к ответу oleksiyp:

Если вам нужно, чтобы это насмешливое поведение было всегда, не только в одном тестовом примере, вы можете смоделировать его, используя @Before а также @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()
}

Таким образом, если вы ожидаете, что больше элементов вашего класса будут использовать класс Uri, вы можете смоделировать его в одном месте, вместо того, чтобы загрязнять свой код .use везде.

После макета 1.8.1:

Версия Mockk 1.8.1 устарела вышеупомянутое решение. После этой версии вы должны сделать:

@Before
fun mockAllUriInteractions() {
    mockkStatic(Uri::class)
    every { Uri.parse("http://test/path") } returns Uri("http", "test", "path")
}

mockkStatic будет очищаться при каждом вызове, поэтому вам больше не нужно его снимать

Остерегаться

Если вы позвоните mockkSatic()без блока не забудь позвонить unmockkStatic()после вызова издевательского метода. Метод не демонтируется автоматически, и вы все равно получите фиктивное значение даже в разных тестовых классах, которые не вызывают mockkStatic(), но используйте статический метод.

Другой вариант — выполнить фиктивный метод внутри блока, после чего он будет автоматически удален:

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

Дополнительно к принятому ответу:

Вы не можете создать Uriвот так, вам придется издеваться над экземпляром Uri. Что-то вроде:

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>()
}

Если мы собираемся имитировать статику, например: mockkStatic(Klass::class)

тогда нам определенно нужно его размокнуть, например: unmockkStatic(Klass::class)

Я бы предложил размокнуть его в методе, аннотированном @After.

Полный пример:

      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()) 
    }
  }
}

Этот пример написан с использованием мок-библиотеки " Mockk " v.1.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 

Как уже упоминалось в нескольких ответах выше, вам необходимо убедиться, что вы вызываете unmockkStatic, иначе вы получите ненадежные тесты (поскольку имитируемый объект/функция будет доступен во всех тестовых классах.

В моем сценарии у меня была функция расширения для всего модуля в kotlin, и для издевательства я использовал сопутствующий объект, как показано ниже:

      class SampleTest {

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

    @AfterAll
    @JvmStatic
    fun teardown() {
        unmockkStatic("packagename.filenameKt")
    }
}}
Другие вопросы по тегам