Grails/Spock: Как издеваться над одним методом внутри класса, где метод вызывается из самого класса?
Учитывая следующее, как я могу mock processMessage() использовать Spock, чтобы я мог проверить, что processBulkMessage() вызывает processMessage() n раз, где n - количество сообщений в BulkMessage?
class BulkMessage {
List messages
}
class MyService {
def processBulkMessage(BulkMessage msg) {
msg.messages.each {subMsg->
processMessage(subMsg)
}
}
def processMessage(Message message) {
}
}
Ответы
Ответ 1
Вы можете использовать spies и частичные mocks (требуется Spock 0.7 или новее).
После создания шпиона вы можете прослушивать разговор между вызывающим и реальным объектом, лежащим в основе шпиона:
def subscriber = Spy(SubscriberImpl, constructorArgs: ["Fred"])
subscriber.receive(_) >> "ok"
Иногда желательно как выполнить некоторый код, так и делегировать реальный метод:
subscriber.receive(_) >> { String message -> callRealMethod(); message.size() > 3 ? "ok" : "fail" }
Ответ 2
По-моему, это не очень хорошо разработанное решение. Тесты и дизайн идут рука об руку - я рекомендую этот поговорить, чтобы исследовать его лучше. Если есть необходимость проверить, был ли вызван другой метод для объекта, находящегося в стадии тестирования, кажется, что он должен быть перенесен на другой объект с различной ответственностью.
Вот как я это сделаю. Я знаю, как видимость работает в groovy, поэтому учитывайте комментарии.
@Grab('org.spockframework:spock-core:0.7-groovy-2.0')
@Grab('cglib:cglib-nodep:3.1')
import spock.lang.*
class MessageServiceSpec extends Specification {
def 'test'() {
given:
def service = new MessageService()
def sender = GroovyMock(MessageSender)
and:
service.sender = sender
when:
service.sendMessages(['1','2','3'])
then:
3 * sender.sendMessage(_)
}
}
class MessageSender { //package access - low level
def sendMessage(String message) {
//whatever
}
}
class MessageService {
MessageSender sender //package access - low level
def sendMessages(Iterable<String> messages) {
messages.each { m -> sender.sendMessage(m) }
}
}
Ответ 3
Он не использует встроенный Mock API Spock (не уверен, как частично издеваться над объектом), но это должно сделать трюк:
class FooSpec extends Specification {
void "Test message processing"() {
given: "A Bulk Message"
BulkMessage bulk = new BulkMessage(messages: ['a', 'b', 'c'])
when: "Service is called"
def processMessageCount = 0
MyService.metaClass.processMessage { message -> processMessageCount++ }
def service = new MyService()
service.processBulkMessage(bulk)
then: "Each message is processed separately"
processMessageCount == bulk.messages.size()
}
}
Ответ 4
Для Java Spring тестирование пользователей в Spock:
constructorArgs - это путь, но используйте конструктор инъекции. Spy() не позволит вам автоматически устанавливать поля с автоподдержкой.
// **Java Spring**
class A {
private ARepository aRepository;
@Autowire
public A(aRepository aRepository){
this.aRepository = aRepository;
}
public String getOne(String id) {
tryStubMe(id) // STUBBED. WILL RETURN "XXX"
...
}
public String tryStubMe(String id) {
return aRepository.findOne(id)
}
public void tryStubVoid(String id) {
aRepository.findOne(id)
}
}
// **Groovy Spock**
class ATest extends Specification {
def 'lets stub that sucker' {
setup:
ARepository aRepository = Mock()
A a = Spy(A, constructorArgs: [aRepository])
when:
a.getOne()
then:
// Stub tryStubMe() on a spy
// Make it return "XXX"
// Verify it was called once
1 * a.tryStubMe("1") >> "XXX"
}
}
Спок - метод stubbing void на объекте Spy
// **Groovy Spock**
class ATest extends Specification {
def 'lets stub that sucker' {
setup:
ARepository aRepository = Mock()
A a = Spy(A, constructorArgs: [aRepository]) {
1 * tryStubVoid(_) >> {}
}
when:
...
then:
...
}
}