Update a Map in groovy spock framework - groovy

I have the below spock specification and want to update the map from data table. Can some body help achieve this
def "groovy map update"() {
setup: "step1"
Map json = [
user :[
name : 'ABC'
]]
when: "step2"
println ('Before modification:')
println (json)
then: "step3"
json.with {
//user.name = value // this one works
(field) = value // this one does not work
}
println ('After modification:')
println (json)
where:
field | value
'user.name' | 'XYZ'
}

The then section is intended for asserts and not for updates etc. So you have to update the map in the when section and then test the result in the then section. For example like this:
def "groovy map update"() {
setup: 'create json'
Map json = [user: [name: 'ABC']]
when: 'update it'
def target = json
for (node in path - path.last()) {
target = target[node]
}
target[path.last()] = value
then: 'check the assignment'
json.user.name == value
where:
path | value
['user', 'name'] | 'XYZ'
}
One way how to update nested Map value can be by using list of path nodes instead of field notation and then iterate over them to obtain the last Map instance and set the value there:
def target = json
for (node in path - path.last()) {
target = target[node]
}
target[path.last()] = value

The accepted solution is correct, I just want to show an alternative doing the same in a slightly different way, assuming you want to stick with the dotted notation for field in your where: block. I just added two more test cases in order to make sure it works as expected.
#Unroll
def "set #field to #value"() {
setup: 'create json'
Map json = [user: [name: 'ABC', address: [street: '21 Main St', zip: '12345', city: 'Hometown']]]
when: 'update it'
def subMap = json
field.split("[.]").each {
if (subMap[it] instanceof Map)
subMap = subMap[it]
else
subMap[it] = value
}
println json
then: 'check the assignment'
json.newField == value ||
json.user.name == value ||
json.user.address.zip == value
where:
field | value
'newField' | 'dummy'
'user.name' | 'XYZ'
'user.address.zip' | '98765'
}
Update: If you want to save a few lines of code you can also use a fold (or reduce or accumulate) operation via inject(..) as described here
#Unroll
def "set #field to #value"() {
setup: 'create json'
Map json = [user: [name: 'ABC', address: [street: '21 Main St', zip: '12345', city: 'Hometown']]]
when: 'update it'
field.split("[.]").inject(json) { subMap, key ->
subMap[key] instanceof Map ? subMap[key] : subMap.put(key, value)
}
println json
then: 'check the assignment'
json.newField == value ||
json.user.name == value ||
json.user.address.zip == value
where:
field | value
'newField' | 'dummy'
'user.name' | 'XYZ'
'user.address.zip' | '98765'
}
Whether you find that readable or not may depend on your familiarity with topics like functional programming in general or map/reduce in particular. The charm here in addition to brevity is that we no longer need a local variable outside of our closure but we just inject (hence the method name) the result of iteration n to iteration n+1.
BTW, as a nice side effect inject(..) as I am using it here returns the previous value of the value you set or overwrite. Just add println in front of field.split("[.]").inject(json) ... in order to see it.
Update 2: Please note that both variants only work if there is no existing field value of type Map in the target field because of the instanceof Map check heuristics in my code. I.e. these two cases would not work:
'user.address' | [street: '23 Test Blvd', zip: '33333', city: 'Somewhere']
'user.address' | '23 Test Blvd, 33333 Somewhere'
This one would work, though, because there is no preexisting value:
'user.alternativeAddress' | [street: '23 Test Blvd', zip: '33333', city: 'Somewhere']

Related

How can I search and return the values and pass it to the method from spock table

Currently implementing GEB,Spock,Groovy. I come across the scenario like
There is a set of data's in the spock table. I have to pass the modulename as a parameter, Search from the spock table then return two values user id and password. Below code is skeleton code
My question is how to search module name based on parameter?
How to return two data's ?
Class Password_Collection extends Specification {
def "Secure password for search and Data Driven"(String ModuleName) {
expect:
// Search based on modulename in where
// pick the values and return the picked data
where:
Module | User_Name | Pass_word
login_Pass | cqauthor1 | SGVsbG8gV29ybGQ =
AuthorPageTest_Pass | cqauthor2 | DOIaRTd35f3y4De =
PublisherPage_pass | cqaauthor3 | iFK95JKasdfdO5 ==
}
}
If you provide the code it would be great help to learn and imeplement.
You don't need to search the table yourself or pick that data. Spock will do that automatically for you
In the expect: block just write your unit test that uses Module, User_Name and Pass_word. Spock will automatically run the test 3 times (as many as the rows of the table) passing each row in turn to your test.
Remove the argument ModuleName from the test method. It is not needed.
I suggest you read the Spock documentation on Data Driven tests a bit more.
class YourSpec extends Specification {
def "Secure password for search and Data Driven"(Module, User_Name, Pass_Word) {
expect:
classUnderTest.getUserNameForModule(Module) == User_Name
classUnderTest.getPasswordForModule(Module) == Pass_Word
where:
Module | User_Name | Pass_word
login_Pass | cqauthor1 | SGVsbG8gV29ybGQ =
AuthorPageTest_Pass | cqauthor2 | DOIaRTd35f3y4De =
PublisherPage_pass | cqaauthor3 | iFK95JKasdfdO5 ==
}
}
What Spock will do is run your test one time for each row in the data table from the "where" block, passing Module, User_Name, Pass_Word as parameters and assert your expectations in the "expect" block.
Please refer to Spock Data Driven Testing documentation for more details.

Using groovy Eval to process generated expression

I'm trying to create a field mapping to map fields from user-friendly names to member variables in a variety of domain objects. The larger context is that I'm building up an ElasticSearch query based on user-constructed rules stored in a database, but for the sake of MCVE:
class MyClass {
Integer amount = 123
}
target = new MyClass()
println "${target.amount}"
fieldMapping = [
'TUITION' : 'target.amount'
]
fieldName = 'TUITION'
valueSource = '${' + "${fieldMapping[fieldName]}" + '}'
println valueSource
value = Eval.me('valueSource')
The Eval fails. Here's the output:
123
${target.amount}
Caught: groovy.lang.MissingPropertyException: No such property: valueSource for class: Script1
groovy.lang.MissingPropertyException: No such property: valueSource for class: Script1
at Script1.run(Script1.groovy:1)
at t.run(t.groovy:17)
What's necessary to evaluate the generated variable name and return the value 123? It seems like the real problem is that it's not recognizing that valueSource has been defined, not the actual expression held in valueSource, but that could be wring, too.
You're almost there, but you need to use a slightly different mechanism: the GroovyShell. You can instantiate a GroovyShell and use it to evaluate a String as a script, returning the result. Here's your example, modified to work properly:
class MyClass {
Integer amount = 123
}
target = new MyClass()
fieldMapping = [
'TUITION' : 'target.amount'
]
fieldName = 'TUITION'
// These are the values made available to the script through the Binding
args = [target: target]
// Create the shell with the binding as a parameter
shell = new GroovyShell(args as Binding)
// Evaluate the "script", which in this case is just the string "target.amount".
// Inside the shell, "target" is available because you added it to the shell's binding.
result = shell.evaluate(fieldMapping[fieldName])
assert result == 123
assert result instanceof Integer

Data driven tests in Spock

I'm rewriting some JUnit test into Spock to take advantage of the data driven test style.
I'm struggling a bit with how to provide the verification with something dynamic.
Here's what I have so far:
def "domestic rules"(from, to, oneWay, check) {
expect:
String mealResponse = getMealResponse(new BookingOptions.BookingOptionsBuilder().setFrom(from).setTo(to).setOneWay(oneWay).build());
check(mealResponse)
where:
from | to | oneWay || check
'MNL' | 'PEK' | true || assertNoMeals()
}
def assertNoMeals = {
assert JsonAssert.with(it)
.assertThat('$.links', hasSize(1))
.assertThat('$.links[0].rel', is("http://localhost:9001/api/docs/rels/ink/meal-allocations"))
.assertThat('$.links[0].uri', startsWith("http://localhost:9001/api/tenants/acme/meals/allocations/"));
}
Unfortunately, I get a NullPointerException at the line with the first row of data.
I guess thats because the closure is being run at that point, rather than just declared.
Is there a way to do this better?
Change
def "domestic rules"(from, to, oneWay, check) {
To
#Unroll
def "domestic rules from #from to #to one way #oneWay"() {
def "domestic rules"() {
when: 'get meals using certain parameters'
String mealResponse = getMealResponse(new BookingOptions.BookingOptionsBuilder().setFrom(from).setTo(to).setOneWay(oneWay).build())
then: 'the json response should contain some contents (improve the message here!)'
JsonAssert.with(mealResponse)
.assertThat('$.links', hasSize(1))
.assertThat('$.links[0].rel', is(somethingToUseInAssertions))
where:
from | to | oneWay || somethingToUseInAssertions
'MNL' | 'PEK' | true || 'just some example'
}
The above should help you get in the right track. Notice that you should have some values only in the examples. If you need some logic in the assertions, use a value which indicates what kind of assertion needs to be made... but it's a very bad idea to use a closure as an example.
If you really want to make your test hard to maintain and go ahead and use closures as "values" in your examples, then do something like this:
def "domestic rules"() {
when:
String mealResponse = getMealResponse(new BookingOptions.BookingOptionsBuilder().setFrom(from).setTo(to).setOneWay(oneWay).build())
then:
check(mealResponse)
where:
from | to | oneWay || check
'MNL' | 'PEK' | true || this.&assertNoMeals
}
boolean assertNoMeals(mealResponse) {
assert JsonAssert.with(mealResponse)
.assertThat('$.links', hasSize(1))
.assertThat('$.links[0].rel', is("http://localhost:9001/api/docs/rels/ink/meal-allocations"))
.assertThat('$.links[0].uri', startsWith("http://localhost:9001/api/tenants/acme/meals/allocations/"))
return true // pass!
}
I advise you to learn both Groovy and Spock before writing something that is more reasonable. It's not hard, but it does take at least a few hours!

Mapping list to Map not working

I have a map
["name1":["field1":value1, "field2":value2, "field3":value3],
"name2":["field1":value4, "field2":value5, "field3":value6],
"name3":["field1":value7, "field2":value8, "field3":value9]]
and a list
[name1, name3]
I wanted a result as
["name1":["field1":value1, "field2":value2, "field3":value3],
"name3":["field1":value7, "field2":value8, "field3":value9]]
The code used
result = recomendationOffers.inject( [:] ) { m, v ->
if( !m[ v ] ) {
m[ v ] = []
}
m[ v ] << tariffRecMap[ v.toString() ]
m
}
Now the datatype of the name1 changed from Varchar2(35) to number(10).
I expected the same logic to work but it is not working and I am getting values
["name1":[null], "name3":[null]]
also the value such as 1000000959 is displayed as 1.000000959E9, is this making any difference ?
posting the original values
When I was handling with string, it looked as below
["FBUN-WEB-VIRGIN-10-24-08":["FIXEDLN_ALLOWANCE":0.0,
"OFFER_VERSION_ID":1.000013082E9, "OFFER_TYPE_DESC":"Contract",
"OFFER_NAME":"PM_V 10 50+250 CA", "SMS_ALLOWANCE":250.0,
"VM_TARIFF_FLAG":"N", "IPHONE_IND":"N", "OFFER_MRC":10.5,
"ALLOWANCE08":0.0, "DATA_ALLOWANCE":524288.0, "BB_IND":"N",
"CONTRACT_TERM":24.0, "OFFER_CODE":"FBUN-WEB-VIRGIN-10-24-08",
"ONNET_TEXT_ALLOWANCE":0.0, "VOICE_ALLOWANCE":50.0,
"MMS_ALLOWANCE":0.0, "ONNET_ALLOWANCE":0.0],
Now after the database datatype changed to number from varchar it looks as below where the value in DB is 1000010315
[1.000010315E9:["FIXEDLN_ALLOWANCE":0.0,
"OFFER_VERSION_ID":1.000010315E9, "OFFER_TYPE_DESC":"Sup Voice",
"OFFER_NAME":"VIP - 35 c", "SMS_ALLOWANCE":60000.0,
"VM_TARIFF_FLAG":"N", "IPHONE_IND":"N", "OFFER_MRC":35.0,
"ALLOWANCE08":45000.0, "DATA_ALLOWANCE":2.147483648E9, "BB_IND":"N",
"CONTRACT_TERM":24.0, "OFFER_CODE":"FBUN-MVP-WEB-VIRGIN-35-24-20",
"ONNET_TEXT_ALLOWANCE":0.0, "VOICE_ALLOWANCE":45000.0,
"MMS_ALLOWANCE":0.0, "ONNET_ALLOWANCE":0.0]
Now the datatype of the name1 changed from Varchar2(35) to number(10) ... also the value such as 1000000959 is displayed as 1.000000959E9, is this making any difference ?
Yes, all the difference in the world. That means you're converting a Double (most likely) to a String, and as the String "1000000959" is not equal to "1.000000959E9", you don't get a match.
Not sure from the question which bits are doubles and which bits are Strings... Maybe you could expand with an actual example?
Also, your inject method can be replaced with:
def result = tariffRecMap.subMap( recomendationOffers )

named parameters with default values in groovy

Is it possible to have named parameters with default values in groovy? My plan is to make a sort of object factory, which can be called with no arguments at all in order to get an object with default values. Also, I'd need the functionality to explicitly set any of the params for the object. I believe this is possible with Python keyword arguments, for example.
The code I'm attempting with right now is something like below
// Factory method
def createFoo( name='John Doe', age=51, address='High Street 11') {
return new Foo( name, age, address )
}
// Calls
Foo foo1 = createFoo() // Create Foo with default values
Foo foo2 = createFoo( age:21 ) // Create Foo where age param differs from defaut
Foo foo3 = createFoo( name:'Jane', address:'Low Street 11' ) // You get the picture
// + any other combination available
The real app that I'm working on will have a lot more of parameters and thus a lot more combinations needed.
Thanks
UPDATE:
The factory method I'm planning is for testing purposes. Cannot really touch the actual Foo class and especially not it's default values.
#dmahapatro and #codelarks answere below had a good point in using a Map as a param that gave me an idea of a possible solution. I could create a map with the wanted defaults and override the needed values, and pass that to the factory method. This'll probably do the job and I'll go with that, unless I get a hint of a better approach.
My current approach below
defaults = [ name:'john', age:61, address:'High Street']
#ToString(includeFields = true, includeNames = true)
class Foo {
// Can't touch this :)
def name = ''
def age = 0
def address = ''
}
def createFoo( Map params ) {
return new Foo( params )
}
println createFoo( defaults )
println createFoo( defaults << [age:21] )
println createFoo( defaults << [ name:'Jane', address:'Low Street'] )
NOTE: leftShift operation ( << ) modifies the the original map, so in the above example age will be 21 in the last method call as well. In my case, this is not a problem as the defaults map can be created freshly each time in setup method.
Groovy does that for you by default (map constructor). You would not need a factory method. Here is an example
import groovy.transform.ToString
#ToString(includeFields = true, includeNames = true)
class Foo{
String name = "Default Name"
int age = 25
String address = "Default Address"
}
println new Foo()
println new Foo(name: "John Doe")
println new Foo(name: "Max Payne", age: 30)
println new Foo(name: "John Miller", age: 40, address: "Omaha Beach")
//Prints
Foo(name:Default Name, age:25, address:Default Address)
Foo(name:John Doe, age:25, address:Default Address)
Foo(name:Max Payne, age:30, address:Default Address)
Foo(name:John Miller, age:40, address:Omaha Beach)
UPDATE
#codelark's astrology :). In case the class is not accessible to set default values, you can do like
#ToString(includeFields = true, includeNames = true)
class Bar{
String name
int age
String address
}
def createBar(Map map = [:]){
def defaultMap = [name:'John Doe',age:51,address:'High Street 11']
new Bar(defaultMap << map)
}
println createBar()
println createBar(name: "Ethan Hunt")
println createBar(name: "Max Payne", age: 30)
println createBar(name: "John Miller", age: 40, address: "Omaha Beach")
//Prints
Bar(name:John Doe, age:51, address:High Street 11)
Bar(name:Ethan Hunt, age:51, address:High Street 11)
Bar(name:Max Payne, age:30, address:High Street 11)
Bar(name:John Miller, age:40, address:Omaha Beach)

Resources