Add custom information to Spock Global Extension - spock

I have configured Spock Global Extension and static class ErrorListener inside it. Works fine for test errors when I want to catch feature title and errors if they happen. But how can I add some custom information to the listener?
For example I have test that calls some API. In case it fails I want to add request/response body to the listener (and report it later). Obviously I have request/response inside the feature or I can get it. How can I pass this information to the Listener and read later in the handling code?
package org.example
import groovy.json.JsonSlurper
import org.spockframework.runtime.AbstractRunListener
import org.spockframework.runtime.extension.AbstractGlobalExtension
import org.spockframework.runtime.model.ErrorInfo
import org.spockframework.runtime.model.IterationInfo
import org.spockframework.runtime.model.SpecInfo
import spock.lang.Specification
class OpenBrewerySpec extends Specification{
def getBreweryTest(){
def breweryText = new URL('https://api.openbrewerydb.org/breweries/1').text
def breweryJson = new JsonSlurper().parseText(breweryText)
//TODO catch breweryText for test result reporting if it is possible
expect:
breweryJson.country == 'United States'
}
def cleanup() {
specificationContext.currentSpec.listeners
.findAll { it instanceof TestResultExtension.ErrorListener }
.each {
def errorInfo = (it as TestResultExtension.ErrorListener).errorInfo
if (errorInfo)
println "Test failure in feature '${specificationContext.currentIteration.name}', " +
"exception class ${errorInfo.exception.class.simpleName}"
else
println "Test passed in feature '${specificationContext.currentIteration.name}'"
}
}
}
class TestResultExtension extends AbstractGlobalExtension {
#Override
void visitSpec(SpecInfo spec) {
spec.addListener(new ErrorListener())
}
static class ErrorListener extends AbstractRunListener {
ErrorInfo errorInfo
#Override
void beforeIteration(IterationInfo iteration) {
errorInfo = null
}
#Override
void error(ErrorInfo error) {
errorInfo = error
}
}
}
Create file src/test/resources/META-INF/services/org.spockframework.runtime.extension.IGlobalExtension
and place string "org.example.TestResultExtension" there to enable extension.

I am pretty sure you found my solution here. Then you also know that it is designed to know in a cleanup() methods if the test succeeded or failed because otherwise Spock does not make the information available. I do not understand why deliberately omitted that information and posted a fragment instead of the whole method or at least mentioned where your code snippet gets executed. That is not a helpful way of asking a question. Nobody would know except for me because I am the author of this global extension.
So now after having established that you are inside a cleanup() method, I can tell you: The information does not belong into the global extension because in the cleanup() method you have access to information from the test such as fields. Why don't you design your test in such a way that whatever information cleanup() needs it stored in a field as you would normally do without using any global extensions? The latter is only meant to help you establish the error status (passed vs. failed) as such.
BTW, I even doubt if you need additional information in the cleanup() method at all because its purpose it cleaning up, not reporting or logging anything. For that Spock has a reporting system which you can also write extensions for.
Sorry for not being more specific in my answer, but your question is equally unspecific. It is an instance of the XY problem, explaining how you think you should do something instead of explaining what you want to achieve. Your sample code omits important details, e.g. the core test code as such.

Related

Grails 4 how to get an handle to artifacts in custom command

I need to build a custom command in a Grails 4 application (https://docs.grails.org/4.0.11/guide/single.html#creatingCustomCommands), and I need to get an handle to some Grails Services and Domain classes which I will query as needed.
The custom command skeleton is quite simple:
import grails.dev.commands.*
import org.apache.maven.artifact.Artifact
class HelloWorldCommand implements GrailsApplicationCommand {
boolean handle() {
return true
}
}
While the documentation says that a custom command has access to the whole application context, I haven't found any examples on how to get an handle of that and start accessing the various application artifacts.
Any hints?
EDIT: to add context and clarify the goal of the custom command in order for further recommendation/best practices/etc.: the command reads data from a file in a custom format, persist the data, and writes reports in another custom format.
Will eventually be replaced by a recurrent job, once the data will be available on demand from a third party REST API.
See the project at github.com/jeffbrown/marco-vittorini-orgeas-artifacts-cli.
grails-app/services/marco/vittorini/orgeas/artifacts/cli/GreetingService.groovy
package marco.vittorini.orgeas.artifacts.cli
class GreetingService {
String greeting = 'Hello World'
}
grails-app/commands/marco/vittorini/orgeas/artifacts/cli/HelloCommand.groovy
package marco.vittorini.orgeas.artifacts.cli
import grails.dev.commands.*
class HelloCommand implements GrailsApplicationCommand {
GreetingService greetingService
boolean handle() {
println greetingService.greeting
return true
}
}
EDIT:
I have added a commit at github.com/jeffbrown/marco-vittorini-orgeas-artifacts-cli/commit/49a846e3902073f8ea0539fcde550f6d002b9d89 which demonstrates accessing a domain class, which was part of the question I overlooked when writing the initial answer.

Map constructor does not assign String to Object in Grails

Given the following domain class:
class Dog {
Object name // changing the type to String fixes it
}
And this unit test:
import grails.test.mixin.Mock
import grails.test.mixin.TestMixin
import grails.test.mixin.support.GrailsUnitTestMixin
import spock.lang.*
/**
* See the API for {#link grails.test.mixin.support.GrailsUnitTestMixin} for usage instructions
*/
#TestMixin(GrailsUnitTestMixin)
#Mock([Dog])
class DogSpec extends Specification {
def setup() {
}
def cleanup() {
}
void "test something"() {
Dog dog = new Dog(name:"sparky")
// dog.name = "sparky" // adding this line also fixes it
expect:"fix me"
dog.name == "sparky"
}
}
Running grails test-app fails, but if you change the type of Dog.name to String, it works fine. Debugging brings me to realize that Dog.name never gets assigned and is null. If I were to set dog.name via regular assignment after constructing it as above, the test passes.
This issue does not occur in Groovy Script using the same map constructor assignment.
I want my type to be Object as it varies depending on the use case.
Any idea why this is happening? Is it a bug in Grails?
I did some debugging and I found out that when a field accepts an Object, grails framework doesn't do databinding on this property.
More specifically, the Dog#name field doesn't belongs to the whitelist of properties when It is declared as Object.
That's why this doesn't work.
If you want to debug, see the
grails.web.databinding.DataBindingUtils#getBindingIncludeList(final Object object)
If you put a breakpoint there, you will see how grails generates the whitelist of properties.
I think this makes a lot of sense since It's related to security!
See more about it here
If you wanna bind on an Object field nevertheless, this code may help you:
class Dog {
Object name
static constraints = {
name(bindable: true)
}
}

Programmatically skip a test in Spock

How do I programmatically skip a test in the Spock framework? I know I can annotate a test with #Ignore to skip it, or use #IgnoreIf to skip tests based on environmental variables and the like. But is there a way to run arbitrary code that decides whether or not a test should run?
For example, let's say I have an integration test that has to connect to a third-party service's sandbox environment. Outages in the service's sandbox environment cause the test to fail. Assuming I've written a method canConnectToService that checks if the test will be able to connect to this service, how do I write a test that will be skipped if canConnectToService() returns false?
Use JUnit's Assume class. Specifically, you could write Assume.assumeTrue(canConnectToService()) at the beginning of your test to skip the test if the third party service is unavailable. This method will throw an AssumptionViolatedException if canConnectToService() returns false, and Spock ignores tests that are interrupted by an AssumptionViolatedException for JUnit compatibility (see this bug report).
There is another alternative (maybe it didn't exists before):
Using instance inside #Requires or #IgnoreIf:
Examples using inheritance, but not required:
abstract class BaseTest extends Specification {
abstract boolean serviceIsOnline()
#Requires({ instance.serviceIsOnline() })
def "some test" () { .. }
}
SubSpecification:
class OnlineTest extends BaseTest {
boolean serviceIsOnline() {
// Test connection, etc.
return true
}
}
class SkipTest extends BaseTest {
boolean serviceIsOnline() {
return false
}
}
Documentation
instance
The specification instance, if instance fields, shared
fields, or instance methods are needed. If this property is used, the
whole annotated element cannot be skipped up-front without executing
fixtures, data providers and similar. Instead, the whole workflow is
followed up to the feature method invocation, where then the closure
is checked, and it is decided whether to abort the specific iteration
or not.
As an extra, another way you can programmatically skip a test is using the where label:
class MyTest extends Specification {
List getAvailableServices() {
// You can test connections here or your conditions
// to enable testing or not.
return available
}
#Unroll
def "Testing something"() {
setup:
URL url = serviceUrl.toURL()
expect:
assert url.text.contains("Hello")
where:
serviceUrl << availableServices
}
}

How to test logic in constructor?

My test class like this:
public class HandlerTest extends Specification {
Handler hander
EventBus eventBus=Mock()
def setup(){
handler=new Handler(eventBus)
}
def "constructor"(){
//How to verify two events do get added to eventBus?
}
}
and Constructor of Handler(it is a java class)
public Handler(EventBus eventBus)
{
eventBus.add(FetchSearchWordsEvent.TYPE, this);
eventBus.add(SetSearchBoxTextEvent.TYPE, this);
}
Question is:
how to verify that two events do get registered?
I would move the call to Handler constructor into the test itself given that it is the function under test.
Try the following:
public class HandlerTest extends Specification {
Handler hander
def mockEventBus = Mock(EventBus)
def "constructor"(){
when:
new Handler(mockEventBus)
then:
1 * mockEventBus.add(FetchSearchWordsEvent.TYPE, _ as Handler)
1 * mockEventBus.add(SetSearchBoxTextEvent.TYPE, _ as Handler)
}
}
The functionality of EventBus.add() should be tested separately.
It depends on how registerHandler is implemented, and what exactly you want to verify. If the goal is to verify that the constructor ultimately calls some methods on eventBus, you can just use regular mocking. If the goal is to verify that the constructor calls registerHandler on itself, you can use partial mocking using Spy(), as explained in the Spock reference documentation.
PS: Note that partial mocking is considered a smell. Often it's better to change the class under test to make it easier to test. For example, you could add a method that allows to query which handlers have been registered. Then you won't need mocking at all.

Scalatest sharing service instance across multiple test suites

I have FlatSpec test classes which need to make use of a REST service for some fixture data. When running all the tests in one go I only really want to instantiate the REST client once as it may be quite expensive. How can I go about this and can I also get it to work for running just one test class when I am running in my IDE?
1. Use mocking:
I would advice you to use some kind of mocking when you try to test REST service. You can try for example scala-mock. Creation of mock service isn't time/cpu consuming, so you can create mocks in all your tests and don't need to share them.
Look:
trait MyRestService {
def get(): String
}
class MyOtherService(val myRestService: MyRestService) {
def addParentheses = s"""(${myRestService.getClass()})"""
}
import org.scalamock.scalatest.MockFactory
class MySpec extends FreeSpec with MockFactory {
"test1 " in {
// create mock rest service and define it's behaviour
val myRestService = mock[MyRestService]
val myOtherService = new MyOtherService(myRestService)
inAnyOrder {
(myRestService.get _).expects().returning("ABC")
}
//now it's ready, you can use it
assert(myOtherService.addParentheses === "(ABC)")
}
}
2. Or use Sharing fixtures:
If you still want to use real implementation of you REST service and create only one instance and then share it with some test condider using:
get-fixture methods => Use it when you need the same mutable fixture objects in multiple tests, and don't need to clean up after.
withFixture(OneArgTest) => Use when all or most tests need the same fixtures that must be cleaned up afterwords.
Refer to http://www.scalatest.org/user_guide/sharing_fixtures#loanFixtureMethods for more details and code examples.
If you want to share the same fixture against multiple Suites use org.scalatest.Suites and #DoNotDiscover annotation (these requires at least scalatest-2.0.RC1)
Pawel's last comment fits well.
It was easier by inheriting from Suite with BeforaAndAfterAll instead of Suites.
import com.typesafe.config.ConfigFactory
import com.google.inject.Guice
import org.scalatest.{BeforeAndAfterAll, Suite}
import net.codingwell.scalaguice.InjectorExtensions.ScalaInjector
class EndToEndSuite extends Suite with BeforeAndAfterAll {
private val injector = {
val config = ConfigFactory.load
val module = new AppModule(config) // your module here
new ScalaInjector(Guice.createInjector(module))
}
override def afterAll {
// your shutdown if needed
}
override val nestedSuites = collection.immutable.IndexedSeq(
injector.instance[YourTest1],
injector.instance[YourTest2] //...
)
}

Resources