I'm looking to perform custom assertions on fields in JSON loaded from file.
I understand that we have fuzzy matching, but I'd like to perform something more custom e.g. have a function which parses a date as a LocalDateTime:
public class DateUtil {
public static boolean matchesMyDateFormat(String dateStr) {
try {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
LocalDateTime.parse(dateStr, formatter);
} catch (DateTimeParseException e) {
return false;
}
return true;
}
}
This would be called by the following:
* def matchesMyDateFormat =
"""
function fn(dateX){
return Java.type('com.karate.DateUtil').matchesMyDateFormat(dateX);
}
"""
* def expected = read('expected.json')
* def actual = read('actual.json')
* match expected == actual
Where expected.json looks like this:
{
"date1" : "#? matchesMyDateFormat(_)"
}
NB this is specifically for JSON loaded from file and not on JSON which is specified in the feature file itself (e.g. like for isValidTime() here: https://github.com/intuit/karate/blob/master/karate-junit4/src/test/java/com/intuit/karate/junit4/demos/schema-like.feature).
Few reasons for wishing to do it this way:
Some of the payloads I need to assert have a lot of date fields coming back, with different formats. Asserting like the above would tie in nicely with Karate's excellent way of validating schema. Doing this in the feature files, however, would require a lot of code, i.e. a line of code for each date (I realize match each could be used - but even this would get complex, depending on the nesting of the fields.)
I would be able to add this function to my common utils feature file, so it can be re-used throughout the project's expected response schema.
Beyond this I'd be looking to do other things, like check that one date occurs before another (but I'd want to do this using various types in Java, e.g. taking time zone into consideration).
I'd also be looking for the format matching method to take in another param, which lets the tester specify a custom format string.
NB: I've read through the docs and the other SO answers related to date assertions and believe this is a slightly different ask.
Is the above possible to do in Karate at the moment?
You can add functions in karate-config.js which will be "global". For example:
var config = {};
config.isValidDate = read('classpath:is-valid-date.js');
return config;
Now you can use isValidDate(_) in any feature. Note that JS functions can take multiple arguments, e.g:
* match foo == { bar: "#? isValidDate(_, 'MYFORMAT')" }
In 0.9.6.RC4 we made improvements so that you can move complex conditional logic and even match operations into re-usable JS files: https://github.com/intuit/karate/issues/1202
Be warned, doing a lot of this may lead to un-readable tests: https://stackoverflow.com/a/54126724/143475
One hint, you can use karate.forEach() to extract all date-fields into an array and then a single match each may work.
Finally, if you still feel there is "too much code in your feature files", I don't know, maybe you need to consult a magician.
What I was trying was in fact a valid use-case (as is the alternative solution kindly suggested by Peter Thomas in his answer).
The reason my particular variation wasn't working was this error:
07:22:50.421 assertion failed: path: $.date1, actual: '#? matchesMyDateFormat(_)', expected: '2020-06-10T14:44:57.060Z', reason: not equal
I noticed with a fresh pair of eyes that I should flip the match statement from:
* match expected == actual
To:
* match actual == expected
This way is required in order for Karate to work its magic and call the custom function in expected.json.
Related
I have a requirement where I need to make operation on Strings. This operation is expensive at runtime, and need to be made only on static expression. Therefore I want to do it at compile time.
Since the operation need to be made a lot of time, in many different modules, having to copy paste, doing manually the conversion, to past the result in the source code is long and slow.
I would like to add an annotation on the Strings which is required to convert, and have a file generated with all the results.
An example could be
val original = #Convert "Hello world"
functionRequiringTheConvertedStringValue(DATA.get(original))
original being equal to "Hello world" and the Data.get function giving the result.
With the DATA class being a static file containing all the string converted and able to fetch them.
Therefore I want to use an annotation processing, but when reading the examples, and the examples, I can't find a way to achieve the logic I want to implement.
I created a SymbolProcessor, but I am not able to get any expression, I can only get functions (KSFunctionDeclarationImpl) and classes (KSClassDeclarationImpl).
...
override fun process(resolver: Resolver): List<KSAnnotated> {
val symbols = resolver.getSymbolsWithAnnotation("com.example.annotation.Convert")
symbols.forEach {
logger.warn("class: ${it.javaClass.name}")
}
val ret = symbols.filter { !it.validate() }.toList()
return ret
}
When I use the annotation on an expression, I do not get it using the getSymbolsWithAnnotation.
My question being, how to get the expression and their value which are annotated by the annotation I created.
Edit:
Seems to be an open issued : https://github.com/google/ksp/issues/1194
Find the example here.
def a = condition ? " karate match statement " : "karate match statement"
Is it possible to do something like this??
This is not recommended practice for tests because tests should be deterministic.
The right thing to do is:
craft your request so that the response is 100% predictable. do not worry about code-duplication, this is sometimes necessary for tests
ignore the dynamic data if it is not relevant to the Scenario
use conditional logic to set "expected value" variables instead of complicating your match logic
use self-validation expressions or schema-validation expressions for specific parts of the JSON
use the if keyword and call a second feature file - or you can even set the name of the file to call dynamically via a variable
in some cases karate.abort() can be used to conditionally skip / exit early
That said, if you really insist on doing this in the same flow, Karate allows you to do a match via JS in 0.9.6.RC4 onwards.
See this thread for details: https://github.com/intuit/karate/issues/1202#issuecomment-653632397
The result of karate.match() will return a JSON in the form { pass: '#boolean', message: '#string' }
If none of the above options work - that means you are doing something really complicated, so write Java interop / code to handle this
Find the example here.
def a = condition ? " karate match statement " : "karate match statement"
Is it possible to do something like this??
This is not recommended practice for tests because tests should be deterministic.
The right thing to do is:
craft your request so that the response is 100% predictable. do not worry about code-duplication, this is sometimes necessary for tests
ignore the dynamic data if it is not relevant to the Scenario
use conditional logic to set "expected value" variables instead of complicating your match logic
use self-validation expressions or schema-validation expressions for specific parts of the JSON
use the if keyword and call a second feature file - or you can even set the name of the file to call dynamically via a variable
in some cases karate.abort() can be used to conditionally skip / exit early
That said, if you really insist on doing this in the same flow, Karate allows you to do a match via JS in 0.9.6.RC4 onwards.
See this thread for details: https://github.com/intuit/karate/issues/1202#issuecomment-653632397
The result of karate.match() will return a JSON in the form { pass: '#boolean', message: '#string' }
If none of the above options work - that means you are doing something really complicated, so write Java interop / code to handle this
Find the example here.
def a = condition ? " karate match statement " : "karate match statement"
Is it possible to do something like this??
This is not recommended practice for tests because tests should be deterministic.
The right thing to do is:
craft your request so that the response is 100% predictable. do not worry about code-duplication, this is sometimes necessary for tests
ignore the dynamic data if it is not relevant to the Scenario
use conditional logic to set "expected value" variables instead of complicating your match logic
use self-validation expressions or schema-validation expressions for specific parts of the JSON
use the if keyword and call a second feature file - or you can even set the name of the file to call dynamically via a variable
in some cases karate.abort() can be used to conditionally skip / exit early
That said, if you really insist on doing this in the same flow, Karate allows you to do a match via JS in 0.9.6.RC4 onwards.
See this thread for details: https://github.com/intuit/karate/issues/1202#issuecomment-653632397
The result of karate.match() will return a JSON in the form { pass: '#boolean', message: '#string' }
If none of the above options work - that means you are doing something really complicated, so write Java interop / code to handle this
I'd like to use UUID as action parameters. However, unless I use .toString() on the UUID objects when generating the action URL's, Play seems to serialize the object differently; Something like this: referenceId.sequence=-1&referenceId.hashCode=1728064460&referenceId.version=-1&referenceId.variant=-1&referenceId.timestamp=-1&referenceId.node=-1
However, using toString "works", but when I redirect from one action to another by simply invoking the method directly, there's no way I can call toString, as the method expects a UUID. Therefore it gives me the representation shown above.
Is there any way I can intersect the serialization of a certain type?
aren't you able to just use string in your action parameter? you know that this string is an UUID, so you can always recreate UUID from it. Maybe this is not the solution for you but that's my first thought. As far as I know play serializes objects like that when passing them trough paremeters.
If this does not work for you try finding something here: http://www.playframework.org/documentation/1.2.4/controllers
I found a way to do this, but right now it means hacking a part of the frameworks code itself.
What you basically need is a TypeBinder for binding the value from the String to the UUID
and a small code change in
play/framework/src/play/data/binding/Unbinder.java
if (!isAsAnnotation) {
// We want to use that one so when redirecting it looks ok. We could as well use the DateBinder.ISO8601 but the url looks terrible
if (Calendar.class.isAssignableFrom(src.getClass())) {
result.put(name, new SimpleDateFormat(I18N.getDateFormat()).format(((Calendar) src).getTime()));
} else {
result.put(name, new SimpleDateFormat(I18N.getDateFormat()).format((Date) src));
}
}
}
//here's the changed code
else if (UUID.class.isAssignableFrom(src.getClass()))
{
result.put(name, src.toString());
}
else {
// this code is responsible for the behavior you're seeing right now
Field[] fields = src.getClass().getDeclaredFields();
for (Field field : fields) {
if ((field.getModifiers() & BeanWrapper.notwritableField) != 0) {
// skip fields that cannot be bound by BeanWrapper
continue;
}
I'm working with the framework authors on a fix for this. will come back later with results.
if you need this urgently, apply the change to the code yourself and rebuild the framework by issuing
ant
in the playframework/framework
directory.