While running Karate(1.0.1) tests from Spock, System property that was set in mock ends up undefined in karate.properties['message'] - karate

In karate version 0.9.5 I was able to use System.setProperty('message', message) during a mock invocation. Then that property was available inside a feature using karate.properties['message']. I have upgraded to version 1.0.1 and now result of karate.properties['message'] results in undefined
Spock Test code
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class ApiTestRunnerSpec extends Specification {
#LocalServerPort
private int port
#SpringBean
MessageLogger messageLogger = Mock()
def "setup"() {
System.out.println("Running on port: " + port)
System.setProperty("server.port", "" + port)
}
def "Run Mock ApiTest"() {
given:
System.setProperty('foo', 'bar')
when:
Results results = Runner.path("classpath:").tags("~#ignore").parallel(5)
then:
results != null
1 * messageLogger.logMessage(_ as String) >> { String message ->
assert message != null
System.setProperty("message", message)
}
}
}
Controller
#RestController
public class MessageController {
#Autowired private MessageLogger messageLogger;
#GetMapping("/message")
public String message() {
String message = "Important Message";
messageLogger.logMessage(message);
return message;
}
}
MessageLogger
#Component
public class MessageLogger {
public void logMessage(String message) {
System.out.println(message);
}
}
karate-config.js
function fn() {
karate.configure('connectTimeout', 10000);
karate.configure('readTimeout', 10000);
karate.configure('ssl', true);
var config = {
localUrl: 'http://localhost:' + java.lang.System.getProperty('server.port'),
};
print('localUrl::::::::::', config.localUrl);
return config;
}
Feature
#mockMessage
#parallel=true
Feature: Test Message
Background:
* url localUrl
Scenario: GET
Given path '/message'
When method get
Then status 200
* print 'foo value ' + karate.properties['foo']
* print 'message value ' + karate.properties['message']
0.9.5
2021-04-28 15:07:51.819 (...) [print] **foo value bar**
2021-04-28 15:07:51.826 (...) [print] **message value Important Message**
1.0.1
2021-04-28 14:36:58.566 (...) [print] **foo value bar**
2021-04-28 14:36:58.580 (...) [print] **message value undefined**
Link to project on github

I cloned your project and noticed a few outdated things (Groovy, Spock and GMaven+ versions). Upgrading them did not change the outcome, I can still reproduce your problem.
A also noticed that in your two branches the POM differs in more than just the Karate version number, also the dependencies differ. If I use the ones from the 1.0.1 branch, tests do not work under 0.9.5 anymore. So I forked your project and sent you two pull requests for each branch with a dependency setup working identically for both Karate versions. Now the branches really just differ in the Karate version number:
https://github.com/kriegaex/spock-karate-example/compare/karate-0.9.5...kriegaex:karate-1.0.1
BTW, for some reason I had to compile your code running JDK 11, JDK 16 did not work. GMaven+ complained about Java 16 groovy class files (bytecode version 60.0), even though GMaven+ should have used target level 11. No idea what this is about. Anyway, on Java 11 I can reproduce your problem. As the Spock version is identical for both branches, I guess the problem is within Karate itself. I recommend to open an issue there, linking to your GitHub project (after you have accepted my PRs). Spock definitely sets the system property, I have added more log output into the stubbing closure order to verify that. Maybe this is an issue concerning how and when Karate communicates with Spock.
Update: Peter Thomas suggested in his answer to store the value to be transferred to the feature in a Java object and access that one from the feature after the Spock test has set it. I guess, he means something like this:
https://github.com/kriegaex/spock-karate-example/commit/ca88e3da
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class ApiTestRunnerSpec extends Specification {
#LocalServerPort
private int port
#SpringBean
MessageLogger messageLogger = Mock() {
1 * logMessage(_ as String) >> { String message ->
assert message != null
MessageHolder.INSTANCE.message = message
}
}
def "setup"() {
System.out.println("Running on port: " + port)
System.setProperty("server.port", "" + port)
}
def "Run Mock ApiTest"() {
given:
Results results = Runner
.path("classpath:")
.systemProperty("foo", "bar")
.tags("~#ignore")
.parallel(5)
expect:
results
}
static class MessageHolder {
public static final MessageHolder INSTANCE = new MessageHolder()
private String message
private MessageHolder() {}
String getMessage() {
return message
}
void setMessage(String message) {
this.message = message
}
}
}
#mockMessage
#parallel=true
Feature: Test Message
Background:
* url localUrl
Scenario: GET
Given path '/message'
When method get
Then status 200
* print 'foo value ' + karate.properties['foo']
* def getMessage =
"""
function() {
var MessageHolder = Java.type('com.example.spock.karate.ApiTestRunnerSpec.MessageHolder');
return MessageHolder.INSTANCE.getMessage();
}
"""
* def message = call getMessage {}
* print 'message value ' + message
Update 2: This is the implementation of Peter's second idea to simply access Java system properties via JS. So I simplified the working, but unnecessarily complicated version with the message holder singleton, eliminating it again:
https://github.com/kriegaex/spock-karate-example/commit/e235dd71
Now it simply looks like this (similar to the original Spock specification, only refactored to be a bit less verbose):
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class ApiTestRunnerSpec extends Specification {
#LocalServerPort
private int port
#SpringBean
MessageLogger messageLogger = Mock() {
1 * logMessage(_ as String) >> { String message ->
assert message != null
System.setProperty('message', message)
}
}
def "setup"() {
System.out.println("Running on port: " + port)
System.setProperty("server.port", "" + port)
}
def "Run Mock ApiTest"() {
expect:
Runner.path("classpath:").systemProperty("foo", "bar").tags("~#ignore").parallel(5)
}
}
The only important change is in the Karate feature:
#mockMessage
#parallel=true
Feature: Test Message
Background:
* url localUrl
Scenario: GET
Given path '/message'
When method get
Then status 200
* print 'foo value ' + karate.properties['foo']
* def getMessage = function() { return Java.type('java.lang.System').getProperty('message'); }
* print 'message value ' + getMessage()

The Runner "builder" has a .systemProperty() method which is recommended.
Please refer: https://github.com/intuit/karate/wiki/1.0-upgrade-guide#improved-test-suite-builder
So this should work. Else as I said in the comments, please submit a way to replicate.
Results results = Runner.path("classpath:")
.systemProperty("foo", "bar")
.tags("~#ignore").parallel(5)
EDIT: so I can confirm that karate.properties is made immutable at the time the test-suite starts.
So here are the 3 options:
change your test strategy so that all dynamic params are resolved before you start
instead of karate.properties[] do the old-school java.lang.System.getProperty('foo') call in Karate / JS, I'm pretty sure that will work
use a Java singleton to hold shared state for your test-runner and karate-feature

Related

Grails integration tests failing due to i18n message fetched during Bootstrap

I added a service method call to Boostrap.groovy in my Grails 4.0.1 application:
#GrailsCompileStatic
class BootStrap {
GrailsApplication grailsApplication
SelfAssessmentRatingService selfAssessmentRatingService
def init = { servletContext ->
TimeZone.setDefault(TimeZone.getTimeZone("UTC"))
...
SelfAssessmentRating rating = SelfAssessmentRating.first()
if (!rating) {
selfAssessmentRatingService.createRatingsFromConfig()
}
}
def destroy = {
}
}
In the service method, an i18n message is fetched for each object I have specified in setup.selfAssessmentRatings in the application config.
#GrailsCompileStatic
#Slf4j
#Transactional
class SelfAssessmentRatingService implements GrailsConfigurationAware {
MessageSource messageSource
List<Map> setupAssessmentRatings
#Override
void setConfiguration(Config co) {
List selfAssessmentRatings = co.getProperty('setup.selfAssessmentRatings', List, null)
setupAssessmentRatings = selfAssessmentRatings as List<Map>
}
/**
* Saves a SelfAssessmentRating record for each one specified in the application config.
* This method gets called if there are no SelfAssessmentRating records saved.
*/
def createRatingsFromConfig() {
if (setupAssessmentRatings == null || setupAssessmentRatings.empty) {
log.info("Skipping creating SelfAssessmentRatings because none were specified " +
"in the config: 'setup.selfAssessmentRatings'")
return
}
log.info("Saving ${setupAssessmentRatings.size()} new SelfAssessmentRating records")
for (Map map in setupAssessmentRatings) {
// SelfAssessmentRating is a domain class
SelfAssessmentRating newRating = new SelfAssessmentRating()
newRating.rating = map.rating as Integer
newRating.englishText = map.englishText
newRating.translationKey = map.translationKey
newRating.save(failOnError: true)
// Verify we have that translation. This will throw an exception if the translation is not present.
messageSource.getMessage(newRating.translationKey, [].toArray(), Locale.default)
}
}
}
Here is the default config value for setup.selfAssessmentRatings; this is in the root of application.yml so it applies to all Grails environments:
setup.selfAssessmentRatings:
- {rating: 1, translationKey: "example.selfAssessment.noExperience", englishText: "No Experience or Knowledge"}
- {rating: 2, translationKey: "example.selfAssessment.someExperience", englishText: "Some Experience or Knowledge"}
- {rating: 3, translationKey: "example.selfAssessment.functionIndependently", englishText: "Able to function independently in this area"}
And I have those three messages defined in grails-app/i18n/messages.properties:
example.selfAssessment.noExperience=No Experience or Knowledge
example.selfAssessment.someExperience=Some Experience or Knowledge
example.selfAssessment.functionIndependently=Able to function independently in this area
My integration tests all pass in CircleCI (./gradlew -Dgrails.env=circleci integrationTest), but on my local machine I get the following error for each integration test:
org.springframework.context.NoSuchMessageException: No message found under code 'example.selfAssessment.noExperience' for locale 'en_US'.
at org.springframework.context.support.AbstractMessageSource.getMessage(AbstractMessageSource.java:161)
at com.hclabs.ojt.step.SelfAssessmentRatingService.$tt__createRatingsFromConfig(SelfAssessmentRatingService.groovy:46)
Does anyone know how I can get my integration tests to pass again locally ?
Is there an argument I could add or an environment variable to set when calling ./gradlew integrationTest on my local machine so that Grails can find the messages in grails-app/i18n/messages.properties?

Groovy spock test isn't stubbing correctly -- receiving null

For some reason (I'm not very good with Spock and am new to mocking & stubbing) I can't get a particular method to stub with what I want.
I've shortened things down a bit, but basically I have a list of books returned from a search. Each book has a workId. There may be older or newer editions of the book, but they all have the same workId -- If a user chooses, they can grab all those other editions.
Preface
Class: Edition
class Edition {
//some strings, ints, and booleans -- information about the edition
}
Class: EditionResults for grabbing additional editions of a single work
class EditionResults {
List<Edition> editionsList
String parentWorkId
}
And one final class: BulkEditions for packaging multiple works (10 books on a search result page)
class BulkEditions {
List<EditionResults> bulkEditions
}
This controller is where I'm having trouble
#RequestMapping('/api')
class EditionsController {
#Autowired
private EditionsService editionsService
private Logger logger = LoggerFactory.getLogger(EditionsController)
#RequestMapping(value = '/bulkeditions', produces = 'application/json')
ResponseEntity<BulkEditions> getBulkEditions(
#RequestParam(value = 'workIds') Set<String> workIds
) throws Exception
{
BulkEditions newBulkEditions = new BulkEditions(bulkEditions: [])
workIds.each { workId ->
EditionResults editions = editionsService.searchEditions(workId)
logger.info "EditionResults {}", editions
if (editions?.editionsList) {
newBulkEditions.bulkEditions << editions
}
}
new ResponseEntity<BulkEditions>(newBulkEditions, HttpStatus.OK)
}
}
particularly, i'm having trouble testing the true branch of editions?.editionsList -- for some reason I keep receiving null for editions
Here's what my test looks like (it always passes, but clover coverage is failing)
class EditionsControllerSpec extends Specification {
private final EditionsService editionsService = Mock(EditionsService)
private static final Set<String> WORK_ID_SET = '12345' as Set<String>
private static final String SINGULAR_WORK_ID = '12345'
#Subject
private final EditionsController controller = new EditionsController(editionsService: editionsService)
void 'test get bulk editions'() {
given:
EditionResults editionResults = Mock(EditionResults)
Edition edition = GroovyMock(Edition) //it's a java class
edition.isAvailable >> true
//edition.bunchOfOtherProps >> randomValues
editionResults.editionsList >> [edition]
editionResults.parentWorkId >> SINGULAR_WORK_ID
editionsService.searchEditions(SINGULAR_WORK_ID) >> editionResults
when:
ResponseEntity<BulkLocalEditions> response = controller.getBulkEditions(WORK_ID_SET)
then:
response
}
My problem is this part:
editionsService.searchEditions(SINGULAR_WORK_ID) >> editionResults
I don't understand why I keep getting this back (from my logger)
[Test worker] INFO [EditionsController:100] [doCall] EditionResults ***null***
My understanding is that editionsService.searchEditions(SINGULAR_WORK_ID) >> editionResults is equivalent to saying "When we hit this searchEditions method, make its result editionResults" -- which we've already populated.... so how is it null?
I've tried being as detailed and clean as I can with the question, simplifying for brevity, but please let me know if there's any additional info needed -- and of course thanks in advance.
Also worth noting that it actually works on my local and everything comes back correctly -- it's just this clover coverage getting me
Thanks to #tim_yates
editionsService.searchEditions(SINGULAR_WORK_ID) >> editionResults should have been in my then block

How to invoke method with sql request in separate class from Groovy script in SOAP UI?

Friends, hello.
I'am trying to use elements of object oriented approach in SOAP UI groovy scripting.
I read manual about "How to write a reusable script" here http://forum.loadui.org/viewtopic.php?f=1&t=15744
and I am trying to write a separate class with method which get data from database and set up address properties (Address).
My problem is that i receive the error message
groovy.lang.MissingPropertyException: No such property: sql1 for class
Utils error at line: 138
when run "main" Groovy script.
"Main" Groovy Script looks like:
// get a reference to the library TestSuite
library = testRunner.testCase.testSuite.project.testSuites["Library"]
// find the module within the library
module = library.testCases["module-name"].testSteps["Utils"]
// initialise the library; which places an instance of Example in the context
module.run(testRunner, context)
// get the instance of example from the context.
def utils = context.utils
// run the method, with parameter
log.info "utils.setAddress() = " + utils.setAddress("LivingPlace");
setAddress method is determined in class Utils which is looked like:
import groovy.sql.Sql;
class Utils
{
def log
def context
def testRunner
// Class constructor with same case as Class name
def Utils(logIn, contextIn, testRunnerIn)
{
this.log = logIn
this.context = contextIn
this.testRunner = testRunnerIn
}
def setAddress (addressType)
{
log.info testRunner;
log.info context;
log.info "Call the DB" ;
com.eviware.soapui.support.GroovyUtils.registerJdbcDriver("com.microsoft.sqlserver.jdbc.SQLServerDriver");
sql1 = Sql.newInstance("jdbc:sqlserver://SRV1:1433; databaseName=DB;", "user", "123", "com.microsoft.sqlserver.jdbc.SQLServerDriver");
sql1.eachRow('select top 1 '+
'Country '+
'from Address')
{ row ->
testRunner.testCase.testSteps['Address'].setPropertyValue("'country", "$row.Country");
}
return addressType //just for test
}
}
context.setProperty ("utils", new Utils(log, context, testRunner) )
log.info "Library Context:"+context;
If i delete everything related with sql it works fine. How to invoke the setAddress method without error described earlier and set Adress properties?

How to find port of Spring Boot container when running a spock test using property server.port=0

Given this entry in application.properties:
server.port=0
which causes Spring Boot to chose a random available port, and testing a spring boot web application using spock, how can the spock code know which port to hit?
Normal injection like this:
#Value("${local.server.port}")
int port;
doesn't work with spock.
You can find the port using this code:
int port = context.embeddedServletContainer.port
Which for those interested in the java equivalent is:
int port = ((TomcatEmbeddedServletContainer)((AnnotationConfigEmbeddedWebApplicationContext)context).getEmbeddedServletContainer()).getPort();
Here's an abstract class that you can extends which wraps up this initialization of the spring boot application and determines the port:
abstract class SpringBootSpecification extends Specification {
#Shared
#AutoCleanup
ConfigurableApplicationContext context
int port = context.embeddedServletContainer.port
void launch(Class clazz) {
Future future = Executors.newSingleThreadExecutor().submit(
new Callable() {
#Override
public ConfigurableApplicationContext call() throws Exception {
return (ConfigurableApplicationContext) SpringApplication.run(clazz)
}
})
context = future.get(20, TimeUnit.SECONDS);
}
}
Which you can use like this:
class MySpecification extends SpringBootSpecification {
void setupSpec() {
launch(MyLauncher.class)
}
String getBody(someParam) {
ResponseEntity entity = new RestTemplate().getForEntity("http://localhost:${port}/somePath/${someParam}", String.class)
return entity.body;
}
}
The injection will work with Spock, as long as you've configured your spec class correctly and have spock-spring on the classpath. There's a limitation in Spock Spring which means it won't bootstrap your Boot application if you use #SpringApplicationConfiguration. You need to use #ContextConfiguration and configure it manually instead. See this answer for the details.
The second part of the problem is that you can't use a GString for the #Value. You could escape the $, but it's easier to use single quotes:
#Value('${local.server.port}')
private int port;
Putting this together, you get a spec that looks something like this:
#ContextConfiguration(loader = SpringApplicationContextLoader, classes = SampleSpockTestingApplication.class)
#WebAppConfiguration
#IntegrationTest("server.port=0")
class SampleSpockTestingApplicationSpec extends Specification {
#Value("\${local.server.port}")
private int port;
def "The index page has the expected body"() {
when: "the index page is accessed"
def response = new TestRestTemplate().getForEntity(
"http://localhost:$port", String.class);
then: "the response is OK and the body is welcome"
response.statusCode == HttpStatus.OK
response.body == 'welcome'
}
}
Also note the use of #IntegrationTest("server.port=0") to request a random port be used. It's a nice alternative to configuring it in application.properties.
You could do this too:
#Autowired
private org.springframework.core.env.Environment springEnv;
...
springEnv.getProperty("server.port");

Seems Like Groovy acts Differently on these two scenarios?

I have two domain classes like this, first namely Manager :
package com.mnm
class Manager {
String name;
static hasMany = [ project : Project, tasks : Tasks ]
static constraints = {
}
}
And second one namely, Project:
package com.mnm
class Project {
String projectTitle
String projectDescription
String description
static belongsTo = [ managers: Manager ]
static hasMany = [ tasks : Tasks ]
static constraints = {
}
}
And I wrote Integration test like this (to find the name of the projects via using Manager) :
void testCountProject() {
def manager = new Manager(name:'Anto').save()
manager.addToProject(new Project(projectTitle:'Grails'))
manager.addToProject(new Project(projectTitle:'Griffon'))
def noOfProjects = Manager.get(manager.id)
def found = noOfProjects.project.collect { it.projectTitle }
assertEquals(['Grails','Griffon'], found.sort())
}
Well there is no error in it and the test passes! But when I add more stuffs into to the same test like (now I'm trying the reverse, finding the Manager name via using Project) :
void testCountProject() {
def manager = new Manager(name:'Anto').save()
def project1 = new Project(projectTitle:'Grails').save()
manager.addToProject(project1)
manager.addToProject(new Project(projectTitle:'Griffon'))
def noOfProjects = Manager.get(manager.id)
def found = noOfProjects.project.collect { it.projectTitle }
assertEquals(['Grails','Griffon'], found.sort())
def noOfManager = Project.get(project.id)
def foundManager = noOfManager.managers.collect { it.name }
assertEquals(['Anto'],foundManager)
}
Now I get the error like this :
No signature of method: com.mnm.Manager.addToProject() is applicable for argument types: (null) values: [null] Possible solutions: addToProject(java.lang.Object), getProject()
Where I went wrong?
Thanks in advance.
You have the same problem in both cases, but the first isn't a proper test so it seems to work. The issue is that all properties are not-null by default, so your Project instances fail validation when you only set projectTitle.
In the first test you don't re-load the manager instance, you're still using the one in-memory because get() uses the Hibernate session as a 1st-level cache. If you flush and clear the session to force it to go to the database it will fail:
class MyTest extends GroovyTestCase {
def sessionFactory
void testCountProject() {
def manager = new Manager(name:'Anto')
manager.addToProject(new Project(projectTitle:'Grails'))
manager.addToProject(new Project(projectTitle:'Griffon'))
manager.save(flush: true)
sessionFactory.currentSession.clear()
def noOfProjects = Manager.get(manager.id)
def found = noOfProjects.project.collect { it.projectTitle }
assertEquals(['Grails','Griffon'], found.sort())
}
}
The second one fails because you call save() on the Project instance and it returns null when validation fails. You don't need to save Project instances because they will be transitively saved when the containing Manager gets saved - the more standard pattern is the one you use in the first test.
You have a few options. One is to fix the validation errors :) Another is to check for validation errors. This requires a separate save() call so you have access to the not-null instance:
def project1 = new Project(projectTitle:'Grails')
project1.save()
if (project1.hasErrors()) {
// handle errors
}
else {
manager.addToProject(project1)
}
The third is failOnError which will throw an exception when validation fails:
def project1 = new Project(projectTitle:'Grails').save(failOnError: true)
manager.addToProject(project1)