Можно ли издеваться над неявным классом Scala? - PullRequest
0 голосов
/ 04 декабря 2018

Я могу расширить свой класс Scala Foo с помощью дополнительных методов через неявный класс:

trait Foo {
  def bar: String
}

object FooExtensions {
  object implicits {
    implicit class FooOps(foo: Foo) {
      def baz: String = "baz"
    }
  }
}

Но могу ли я сменить эти методы?

import org.mockito.Mockito
import org.scalatest.WordSpec
import org.scalatest.mockito.MockitoSugar

class MySpec extends WordSpec with MockitoSugar {
  "My mock" should {
    "handle methods from implicit classes" in {
      import FooExtensions.implicits._
      val foo = mock[Foo]
      Mockito.when(foo.baz).thenReturn("bix") // fails at runtime
    }
  }
}

Это компилируется, ноне удается с

when() requires an argument which has to be 'a method call on a mock'.
For example:
    when(mock.getArticles()).thenReturn(articles);

Also, this error might show up because:
1. you stub either of: final/private/equals()/hashCode() methods.
   Those methods *cannot* be stubbed/verified.
   Mocking methods declared on non-public parent classes is not supported.
2. inside when() you don't call method on mock but on some other object.

org.mockito.exceptions.misusing.MissingMethodInvocationException: 
when() requires an argument which has to be 'a method call on a mock'.
For example:
    when(mock.getArticles()).thenReturn(articles);

Also, this error might show up because:
1. you stub either of: final/private/equals()/hashCode() methods.
   Those methods *cannot* be stubbed/verified.
   Mocking methods declared on non-public parent classes is not supported.
2. inside when() you don't call method on mock but on some other object.

Можно ли имитировать методы, добавленные через неявные классы?Надеюсь, с Mockito (или mockito-scala ), но меня интересует любой подход, который работает.

Ответы [ 2 ]

0 голосов
/ 05 декабря 2018

Единственный способ добиться этого - использовать неявные преобразования, а не неявные классы

Это хак, предназначенный для демонстрации того, как этого можно достичь, но я хотел бы взглянуть накод и понять, почему вам действительно нужно это сделать

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

trait Foo {
  def bar: String
}

object FooExtensions {
  object implicits {
    implicit fooToOps(foo: Foo): FooOps = new FooOps(foo)
    class FooOps(foo: Foo) {
      def baz: String = "baz"
    }
  }
}

и ваш тест

import org.scalatest.WordSpec
import org.mockito.MockitoSugar

  class MySpec extends WordSpec with MockitoSugar {
    "My mock" should {
      "handle methods from implicit classes" in {
        val fooOps = mock[FooOps]
        implicit fooToOps(foo: Foo): FooOps = fooOps
        val foo = mock[Foo]
        when(foo.baz) thenReturn "bix" // works
      }
    }
  }

Другая вещь, которую следует учитывать, заключается в том, что в вашем производстве вам нужно получить неявный параметр формы Foo => FooOps, поэтому при вызове этого метода из теста предоставляется фактическая неявная имитация ...

Как я уже сказал, вы можете заставить его работать так, но я согласен с Матеушем, что вам не нужно

0 голосов
/ 04 декабря 2018

Суть методов расширения в том, что они по сути являются синтаксическим сахаром:

trait Foo
implicit class ExtensionMethods(foo: Foo) {
  def bar: String = "bar
}

foo.bar

равно

new ExtensionMethods(foo).bar

Так что насмешливо:

Mockito.when(foo.bar).thenReturn("bix")

становится:

Mockito.when(new ExtensionMethods(foo).bar).thenReturn("bix")

Я думаю, что нет никакого обходного пути - возможно, PowerMock мог бы позволить вам изменить конструктор класса ..., но с обычным Mockito это невозможно.

Обычно это непроблема, хотя.Это потому, что либо:

  • вы вводите в методы расширения поведение, которое зависит только от расширенного значения и передаваемых параметров (а расширенный метод довольно часто является чистой функцией, не требующей проверки) - если вы хотитечтобы что-то там изменить, вы меняете ввод,
  • , если поведение должно измениться, вы реализуете его внутри класса типов и заставляете метод расширения использовать этот класс типов для внедрения поведения

    trait Bar {
      def bar: String
    }
    object Bar {
      implicit val defaultBar: Bar = new Bar { def bar = "bar" }
    }
    
    implicit class ExtensionMethods(foo: Foo) {
      def bar(implicit bar: Bar): String = bar.bar
    }
    
    // in test
    implicit var overridenBar: Bar = ...
    assert(foo.bar === "sth")
    

С другой стороны: чем больше функциональности вы получите, тем меньше вам нужно будет имитировать вещи, поскольку все будет зависеть только от ввода, переданного внутрь, а каскад имитаторов станет просто запахом кода -слишком сильная связь, слишком большие интерфейсы и т. д. Проблема в том, что многие библиотеки Java даже не следуют принципам SOLID, что делает их сложными в использовании / тестировании с FP, а также с плохим ООП самостоятельно.Я говорю об этом, если вы чувствуете, что насмешка - это единственный способ в вашем деле.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...