I need to generate a list,and name it's items based on for-loop index
number, like this:
for(int i=0;i<someNumber;i++){
Model m_{$i}=Mock() //but this doesn't work
......
models.add(i,m_{$i})
}
then they can be distinguished by name when debugging test code(shame to tell this) within eclipse,but it doesn't work, so how to make it work?
update:add image to tell why I want to append for-loop index to variable name
You can also add some property to your Mock class at runtime thanks to Groovy's MetaClass. Take a look at this sample snippet:
class myClass {
String someProperty
}
def models = []
10.times { it ->
def instance = new myClass(someProperty: "something")
instance.metaClass.testId = it
models.add(instance)
}
// delete some
println "Removing object with testId = " + models.remove(4).testId
println "Removing object with testId = " + models.remove(7).testId
def identifiersOfObjectsAfterRemoves = models.collect { it.testId }
def removedObjectsIdentifiers = (0..9) - identifiersOfObjectsAfterRemoves
println "Identifiers of removed objects: " + removedObjectsIdentifiers
Related
I have JSON looking like:
{
"days": [
{
"mintemp": "21.8"
}
]
}
With Groovy, I parse it like this:
class WeatherRow {
String mintemp
}
def file = new File("data.json")
def slurper = new JsonSlurper().parse(file)
def days = slurper.days
def firstRow = days[0] as WeatherRow
println firstRow.mintemp
But actually, I would like to name my instance variable something like minTemp (or even something completely random, like numberOfPonies). Is there a way in Groovy to map a member of a map passed to a constructor to something else?
To clarify, I was looking for something along the lines of #XmlElement(name="mintemp"), but could not easily find it:
class WeatherRow {
#Element(name="mintemp")
String minTemp
}
Create a constructor that takes a map.
Runnable example:
import groovy.json.JsonSlurper
def testJsonStr = '''
{"days": [
{ "mintemp": "21.8" }
]}'''
class WeatherRow {
String minTemp
WeatherRow(map) {
println "Got called with constructor that takes a map: $map"
minTemp = map.mintemp
}
}
def slurper = new JsonSlurper().parseText(testJsonStr)
def days = slurper.days
def firstRow = days[0] as WeatherRow
println firstRow.minTemp
Result:
Got called with constructor that takes a map: [mintemp:21.8]
21.8
(of course you'd remove the println line, it's just there for the demo)
You can achieve this using annotation and simple custom annotation processor like this:
1. Create a Custom Annotation Class
#Retention(RetentionPolicy.RUNTIME)
#interface JsonDeserializer {
String[] names() default []
}
2. Annotate your instance fields with the custom annotation
class WeatherRow{
#JsonDeserializer(names = ["mintemp"])
String mintemp;
#JsonDeserializer(names = ["mintemp"])
String minTemp;
#JsonDeserializer(names = ["mintemp"])
String numberOfPonies;
}
3. Add custom json deserializer method using annotation processing:
static WeatherRow fromJson(def jsonObject){
WeatherRow weatherRow = new WeatherRow();
try{
weatherRow = new WeatherRow(jsonObject);
}catch(MissingPropertyException ex){
//swallow missing property exception.
}
WeatherRow.class.getDeclaredFields().each{
def jsonDeserializer = it.getDeclaredAnnotations()?.find{it.annotationType() == JsonDeserializer}
def fieldNames = [];
fieldNames << it.name;
if(jsonDeserializer){
fieldNames.addAll(jsonDeserializer.names());
fieldNames.each{i ->
if(jsonObject."$i")//TODO: if field type is not String type custom parsing here.
weatherRow."${it.name}" = jsonObject."$i";
}
}
};
return weatherRow;
}
Example:
def testJsonStr = '''
{
"days": [
{
"mintemp": "21.8"
}
]
}'''
def parsedWeatherRows = new JsonSlurper().parseText(testJsonStr);
assert WeatherRow.fromJson(parsedWeatherRows.days[0]).mintemp == "21.8"
assert WeatherRow.fromJson(parsedWeatherRows.days[0]).minTemp == "21.8"
assert WeatherRow.fromJson(parsedWeatherRows.days[0]).numberOfPonies == "21.8"
Check the full working code at groovyConsole.
I am doing some integration tests with Spock with 3rd party apps. Now I am struggling with a problem that I am not sure wether I am approaching the issue properly or not.
In one of the tests I am connecting to a 3rd party service to get some information in an array. Then each of these items are passed to another method to process them individually.
def get3rdPartyItems = {
[item1, item2, item3]
}
def processItem = { item ->
//do something with item
}
get3rdPartyItems.each {
processItem(it)
}
Then I have a test that connects to real 3rd party service using the method get3rdPartyItems() in which I am testing that processItem is called as many times as items has returned the method get3rdPartyItems().
What I am trying to do is to save one of the items as #Shared variable to write another test to know that the item is processed properly as I don't want to mock the content retrieved from the 3rd party service as I want real data.
Basically, this is what I am doing:
#Shared def globalItem
MyClass.metaClass.processItem = { i ->
if (!globalItem)
globalItem = i
//And now I would need to call the original method processItem
}
Any clue how to achieve this? I am probably overheading too much so I am open to change the solution.
Not sure if this is what you want, as it's hard to see your existing structure from the code and the code isn't runnable as-is, but given this class:
class MyClass {
def get3rdPartyItems = {
['item1', 'item2', 'item3']
}
def processItem( item ) {
println item
//do something with item
}
def run() {
get3rdPartyItems().each {
processItem( it )
}
}
}
You can do this:
def globalItem
def oldProcessItem = MyClass.metaClass.getMetaMethod("processItem", Object)
MyClass.metaClass.processItem = { item ->
if (!globalItem) {
println "Setting global item to $item"
globalItem = item
}
oldProcessItem.invoke( delegate, item )
}
def mc = new MyClass()
new MyClass().run()
Just as a matter of concision, that should be the way of passing the parameters to the metamethod in case you pass multiple parameters:
def globalItem
def oldProcessItem = MyClass.metaClass.getMetaMethod("processItem", ["",[:]] as Object[])
MyClass.metaClass.processItem = { String p1, Map p2 ->
if (!globalItem) {
println "Setting global item to $item"
globalItem = p2
}
oldProcessItem.invoke( delegate, [p1,p2] as Object[] )
}
def mc = new MyClass()
new MyClass().run()
is there a way in Groovy to replace some code like the below:
Task a = new Task('a')
Process p = new Process('p')
with something easier, like:
task a
process p
where task and process can be method calls that create the object and return it or add it to the script Map.
The main problem I currently have is that I cannot use a because it is not defined.
To create objects and name them without assigning to a variable, you can use a binding. Create and keep a reference to the a closure's binding, and have the utility methods task and process associate the new instance with the name. For example:
def scriptBinding = new Binding()
def task = { String name ->
scriptBinding[name] = new Task(name)
}
def process = { String name ->
scriptBinding[name] = new Process(name)
}
def script = {
task 'a'
process 'b'
println a
println b
}
script.binding = scriptBinding
script()
Note that you have to quote a and b so they are interpreted as strings instead of undefined variables. If you'd like to omit quotes, you can use a custom Binding object that evaluates undefined symbols as their string representation like this:
class SymbolAsStringBinding extends Binding {
Object getVariable(String name) {
try {
super.getVariable(name)
} catch (MissingPropertyException e) {
name
}
}
boolean hasVariable(String name) {
true
}
}
With that addition, you can write the script as:
def script = {
task a
process b
println a
println b
}
Try this:
class Task {
String name
Task(name) { this.name = name }
String toString() { "task: $name".toString() }
}
def propertyMissing(String name) { this.getBinding()[name] = name }
def task(name) { this.getBinding()[name] = new Task(name) }
task a
println a
this will produce:
task: a
essentially when you reach the
task a
statement the propertyMissing() will put in the binding a variable named a with content it's name.
Later the task() function will replace the variable a in the binding with the new Task passing as name the name of the missing varible a
If I dynamically add a property to a class, each instance of the class is initialized with a reference to the same value (even though the properties are correctly at different addresses, I don't want them to share the same reference value):
Here's an example:
class SolarSystem {
Planets planets = new Planets()
static main(args) {
SolarSystem.metaClass.dynamicPlanets = new Planets()
// Infinite loop
// SolarSystem.metaClass.getDynamicPlanets = {
// if (!delegate.dynamicPlanets.initialized) {
// delegate.dynamicPlanets = new Planets(initialized: true)
// }
//
// delegate.dynamicPlanets
// }
// No such field: dynamicPlanets for class: my.SolarSystem
// SolarSystem.metaClass.getDynamicPlanets = {
// if (!delegate.#dynamicPlanets.initialized) {
// delegate.#dynamicPlanets = new Planets(initialized: true)
// }
//
// delegate.#dynamicPlanets
// }
SolarSystem.metaClass.getUniqueDynamicPlanets = {
if (!delegate.dynamicPlanets.initialized) {
delegate.dynamicPlanets = new Planets(initialized: true)
}
delegate.dynamicPlanets
}
// SolarSystem.metaClass.getDynamicPlanets = {
// throw new RuntimeException("direct access not allowed")
// }
def solarSystem1 = new SolarSystem()
println "a ${solarSystem1.planets}"
println "b ${solarSystem1.dynamicPlanets}"
println "c ${solarSystem1.uniqueDynamicPlanets}"
println "d ${solarSystem1.dynamicPlanets}"
println ''
def solarSystem2= new SolarSystem()
println "a ${solarSystem2.planets}"
println "b ${solarSystem2.dynamicPlanets}"
println "c ${solarSystem2.uniqueDynamicPlanets}"
println "d ${solarSystem2.dynamicPlanets}"
}
}
In a separate file:
class Planets {
boolean initialized = false
}
When this runs, you see something like this:
a my.Planets#4979935d
b my.Planets#66100363
c my.Planets#5e0feb48
d my.Planets#5e0feb48
a my.Planets#671ff436
b my.Planets#66100363
c my.Planets#651dba45
d my.Planets#651dba45
Notice how for solarSystem2, the 'normal' member variable planets has a different address when the two objects are created. However, the dynamically added dynamicPlanets points to the same object that solarSystem1 pointed to (in this case, at address 66100363).
I can reassign them in my dynamic getter (getUniqueDynamicPlanets), and that fixes the problem.
However, I cannot override the getDynamicPlanets getter, because I either get an infinite loop, or I cannot get direct access to the dynamically-added property.
Is there a way to directly access the dynamically-added property so I could handle this in the getDynamicPlanets getter? Is there a better strategy for this altogether? Sorry if I missed it, I've looked a bunch...
Thanks
I'm not 100% sure I understand your question, but if I do, did you try setting the getDynamicPlanets closure to have explicitly 0 parameters, so:
SolarSystem.metaClass.getDynamicPlanets = {-> ... }
If you don't have the -> with no args before it, there is an implicit it parameter that's assigned and it's not a zero arg method, so doesn't adhere to the javabean getter/setter pattern.
Is it possible to add a property or a method to an object dynamically in Groovy? This is what I have tried so far:
class Greet {
def name
Greet(who) { name = who[0].toUpperCase() + [1..-1] }
def salute() { println "Hello $name!" }
}
g = new Greet('world') // create object
g.salute() // Output "Hello World!"
g.bye = { println "Goodbye, $name" }
g.bye()
But I get the following exception:
Hello World!
Caught: groovy.lang.MissingPropertyException: No such property: bye for class: Greet
Possible solutions: name
at test.run(greet.groovy:11)
If you just want to add the bye() method to the single instance g of the class Greet, you need to do:
g.metaClass.bye = { println "Goodbye, $name" }
g.bye()
Otherwise, to add bye() to all instance of Greet (from now on), call
Greet.metaClass.bye = { println "Goodbye, $name" }
But you'd need to do this before you create an instance of the Greet class
Here is a page on the per-instance metaClass
And here is the page on MetaClasses in general
Also, there's a bug in your constructor. You're missing who from infront of your [1..-1] and if the constructor is passed a String of less than 2 characters in length, it will throw an exception
A better version might be:
Greet( String who ) {
name = who.inject( '' ) { String s, String c ->
s += s ? c.toLowerCase() : c.toUpperCase()
}
}
As metioned in the comments,
Greet( String who ) {
name = who.capitalize()
}
is the proper way