Mock возвращает заглушенный результат для произвольного параметра

В следующем тесте у меня неверная установка заглушки. Макет requestBuilder заглушен параметром "INCORRECT", тогда как тестируемый класс вызывает его с параметром "/socket".

Мой опыт работы с Mockito в JUnit говорит мне, что вызов во время выполнения приведет к null. Тем не менее, я вижу ложное срабатывание. Тест проходит, и requestBuilder возвращает макет request, даже если он вызывается с "/socket".

Почему это? Это как-то связано с наличием более чем одного ожидания? Если да, то учитывается последнее ожидание, и оно должно потерпеть неудачу.

class ChannelSpec extends Specification with Mockito with ScalaCheck with ArbitraryValues { def is = s2"""

  A Channel must
    send an open request to /socket with provided channel name on instantiation $sendToSocket

"""

  def sendToSocket = prop{(name: String, key: Key, secret: Secret) =>
    val requestBuilder = mock[(String, Seq[Map[String, String]]) => Req]
    val request = mock[Req]
    val httpRequestor = mock[(Req) => Future[String]]
    val result = mock[Future[String]]

    val params = Seq(Map("key" -> key.value, "timestamp" -> anyString, "token" -> anyString), Map("channel" -> name))
    requestBuilder("INCORRECT", params) returns request
    httpRequestor(request) returns result
    new Channel(name, key, secret, requestBuilder = requestBuilder, httpRequestor = httpRequestor)
    there was one(requestBuilder).apply("INCORRECT", params)
    println("expecting " + request)
    there was one(httpRequestor).apply(request)
  }

person Synesso    schedule 04.09.2013    source источник


Ответы (2)


Хотя я не знаю Scala, я знаю, что anyString не делает того, что вы думаете. В частности, все сопоставители Mockito работают через побочные эффекты, добавляя связанные объекты String к ArgumentMatcherStorage, как я описал ближе к концу этот ТАК ответ.

Следовательно, вы не можете извлечь совпадения в переменные:

// This is fine, because the parameters are evaluated in order.

takesTwoParameters(eq("A"), eq("B")) returns bar

// Now let's change it a little bit.

val secondParam = eq("SECOND")
val firstParam = eq("FIRST")

// At this point, firstParam == secondParam == null, and the hidden argument
// matcher stack looks like [eq("SECOND"), eq("FIRST")]. That means that your
// stubbing will apply to takesTwoParameters("SECOND", "FIRST"), not
// takesTwoParameters("FIRST", "SECOND")!

takesTwoParameters(firstParam, secondParam)) returns bar

Кроме того, вы не можете вкладывать anyString в произвольные типы, такие как Seq или Map; Сопоставители аргументов Mockito предназначены для сопоставления целых аргументов.

Лучше всего использовать argThat для ваших параметров и создать небольшой сопоставитель Hamcrest, который проверяет, правильно ли сформирован аргумент, который вы проверяете. Это даст вам гибкость проверки того, что ваши параметры примерно соответствуют вашим ожиданиям, игнорируя при этом значения, которые вам не нужны.

person Jeff Bowman    schedule 04.09.2013
comment
Верно. Обычно я сначала пишу свой предполагаемый тест таким образом, а затем жду, когда ошибки hamcrest скажут мне написать более сложный код для всех совпадений или без. Но похоже в Specs2 поведение сопоставителей другое. - person Synesso; 05.09.2013

Я думаю, что это просто проявление того, что в спецификации приемки нет исключений, сигнализирующих о несостоявшихся ожиданиях. Итак, у вас есть:

def test = {
  1 === 2
  1 === 1
}

Тогда test будет пройдено, потому что в качестве результата теста будет сохранено только последнее значение. Вы можете изменить это поведение, объединив ожидания:

def test = {
  (1 === 2) and
  (1 === 1)
}

Или смешав в Спецификации трейт ThrownExpectations:

import org.specs2.Specification
import org.specs2.matcher.ThrownExpectations
import org.specs2.mock.Mockito

class MySpec extends Specification with ThrownExpecations with Mockito {
  ...
}
person Eric    schedule 05.09.2013
comment
Таким образом, в выражении requestBuilder("/socketz", params) returns request исключения hamcrest проглатываются, а макет все равно возвращается. Если я изменю параметры, чтобы не было совпадений, то спецификация не будет работать в последней строке, потому что request равно нулю. - person Synesso; 05.09.2013
comment
Ответ Джеффа Боумена правильный. Тогда мой ответ просто указывает, что there was one(requestBuilder).apply("INCORRECT", params) не будет генерировать исключение в спецификации принятия, даже если заглушка выполнена правильно. - person Eric; 05.09.2013
comment
Я пытался воспроизвести то, что вижу, в упрощенном тестовом примере, но не смог. InvalidUseOfMatchersException выбрасывается, как и ожидалось. Значит, в моей спецификации есть что-то странное, из-за чего сопоставители ошибочно совпадают. - person Synesso; 05.09.2013