JsonPath config for MockMvc - spring

JsonPath itself can be configured like this
import com.jayway.jsonpath.Configuration;
import com.jayway.jsonpath.JsonPath;
import com.jayway.jsonpath.Option;
// ...
Configuration configuration = Configuration
.builder()
.options(Option.REQUIRE_PROPERTIES)
.build();
JsonPath
.using(configuration)
.parse(someJson)
.read("$.*.bar");
The example above enables Option.REQUIRE_PROPERTIES configuration option, so it will throw an exception if path does not exist.
How to configure the same thing for jsonPath used by MockMvc in a spring boot project?
import org.springframework.test.web.servlet.MockMvc;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
// ...
mockMvc
.perform(get("/test"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.*.bar").isEmpty()) // How to configure this 'jsonPath'?
Update
See the following example:
Input
[
{"animal": "cat", "meow": true},
{"animal": "cat", "meow": true},
{"animal": "cat", "bark": true}
]
Expression
jsonPath("$.[?(#.animal == 'cat')].meow").value(everyItem(equalTo(true))
This produces a "false positive" test result. How I could write the json path expression, as I expect this test to produce a failed result.

No, unfortunately, we can't configure JSONPath that fine, in context of spring-test-mockmvc.
Proof (https://github.com/spring-projects/spring-framework/blob/main/spring-test/src/main/java/org/springframework/test/util/JsonPathExpectationsHelper.java#L61):
this.jsonPath = JsonPath.compile(this.expression);
"They" use (internally) a simpler instantiation (without configuration).
(What is wrong with isEmpty(), do you need this exception?)
There are also alternative matchers like:
doesNotExist()
doesNotHaveJsonPath()
If we need this fine config + exact exception, we can still:
Use JsonPath directly (/as a bean) to:
parse (e.g.) from MvcResult.getResponse().getContentAsString()
#Bean
Configuration configuration()
return Configuration
.builder()
.options(Option.REQUIRE_PROPERTIES)
.build();
}
..and then:
#Autowired
Configuration config;
// ...
MvcResult result = mockMvc
.perform(get("/test"))
.andExpect(status().isOk())
.andReturn();
// try/expect:
JsonPath
.using(config)
.parse(result.getResponse().getContentAsString())
.read("$.*.bar");
Refs:
https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/test/web/servlet/result/JsonPathResultMatchers.html
https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/test/web/servlet/MvcResult.html
https://github.com/json-path/JsonPath (in-/definit)

Related

How to redirect Prometheus Metrics to the default spring boot server

I am trying to expose a custom Gauge metric from my Spring Boot Application. I am using Micrometer with the Prometheus registry to do so. I have set up the PrometheusRegistry and configs as per - Micrometer Samples - Github but it creates one more HTTP server for exposing the Prometheus metrics. I need to redirect or expose all the metrics to the Spring boot's default context path - /actuator/prometheus instead of a new context path on a new port. I have implemented the following code so far -
PrometheusRegistry.java -
package com.xyz.abc.prometheus;
import java.io.IOException;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.time.Duration;
import com.sun.net.httpserver.HttpServer;
import io.micrometer.core.lang.Nullable;
import io.micrometer.prometheus.PrometheusConfig;
import io.micrometer.prometheus.PrometheusMeterRegistry;
public class PrometheusRegistry {
public static PrometheusMeterRegistry prometheus() {
PrometheusMeterRegistry prometheusRegistry = new PrometheusMeterRegistry(new PrometheusConfig() {
#Override
public Duration step() {
return Duration.ofSeconds(10);
}
#Override
#Nullable
public String get(String k) {
return null;
}
});
try {
HttpServer server = HttpServer.create(new InetSocketAddress(8081), 0);
server.createContext("/sample-data/prometheus", httpExchange -> {
String response = prometheusRegistry.scrape();
httpExchange.sendResponseHeaders(200, response.length());
OutputStream os = httpExchange.getResponseBody();
os.write(response.getBytes());
os.close();
});
new Thread(server::start).run();
} catch (IOException e) {
throw new RuntimeException(e);
}
return prometheusRegistry;
}
}
MicrometerConfig.java -
package com.xyz.abc.prometheus;
import io.micrometer.core.instrument.MeterRegistry;
public class MicrometerConfig {
public static MeterRegistry carMonitoringSystem() {
// Pick a monitoring system here to use in your samples.
return PrometheusRegistry.prometheus();
}
}
Code snippet where I am creating a custom Gauge metric. As of now, it's a simple REST API to test - (Please read the comments in between)
#SuppressWarnings({ "unchecked", "rawtypes" })
#RequestMapping(value = "/sampleApi", method= RequestMethod.GET)
#ResponseBody
//This Timed annotation is working fine and this metrics comes in /actuator/prometheus by default
#Timed(value = "car.healthcheck", description = "Time taken to return healthcheck")
public ResponseEntity healthCheck(){
MeterRegistry registry = MicrometerConfig.carMonitoringSystem();
AtomicLong n = new AtomicLong();
//Starting from here none of the Gauge metrics shows up in /actuator/prometheus path instead it goes to /sample-data/prometheus on port 8081 as configured.
registry.gauge("car.gauge.one", Tags.of("k", "v"), n);
registry.gauge("car.gauge.two", Tags.of("k", "v1"), n, n2 -> n2.get() - 1);
registry.gauge("car.help.gauge", 89);
//This thing never works! This gauge metrics never shows up in any URI configured
Gauge.builder("car.gauge.test", cpu)
.description("car.device.cpu")
.tags("customer", "demo")
.register(registry);
return new ResponseEntity("Car is working fine.", HttpStatus.OK);
}
I need all the metrics to show up inside - /actuator/prometheus instead of a new HTTP Server getting created. I know that I am explicitly creating a new HTTP Server so metrics are popping up there. Please let me know how to avoid creating a new HTTP Server and redirect all the prometheus metrics to the default path - /actuator/prometheus. Also if I use Gauge.builder to define a custom gauge metrics, it never works. Please explain how I can make that work also. Let me know where I am doing wrong.
Thank you.
Every time you call MicrometerConfig.carMonitoringSystem(); it is creating a new prometheus registry (and trying to start a new server)
You need to inject the MeterRegistry in your class that is creating the gauge and use the injected MeterRegistry that way.

Spring 5 WebClient- retrieve method not getting recognized with groovy but works with java

I am using groovy, spring5, springboot2 and i was trying to replace Resttemplate with WebClient inorder to makes some synchronous HTTP(REST) calls. Retrieve() in webClient.get().uri("").retrieve() is not getting recognized(compilation error) in .groovy while the same code works in .java
WebClientJava.java
import org.springframework.web.reactive.function.client.WebClient;
public class WebClientJava {
String retriever(){
WebClient webClient = WebClient.create();
String responsess = webClient.get().uri("").retrieve().bodyToMono(String.class).block();
return responsess;
}
}
WebClientGroovy.groovy
import org.springframework.web.reactive.function.client.WebClient
class WebClientGroovy {
String retriever(){
WebClient webClient = WebClient.create()
WebClient.RequestHeadersSpec responsess = webClient.get().uri("").retrieve().bodyToMono(String.class).block()
return responsess
}
}
i expect it work with groovy as it is working with java. Does any one encounter or has any thoughts?
Your problem appears to be caused by a bug in IntelliJ IDEA as the sample project builds successfully on the command line with Maven.
The uri method returns S with S being defined as S extends RequestHeadersSpec<?>. The IDE appears to be unable to cope with this and believes it's dealing with a ? which it treats as java.lang.Object. I would recommend reporting the problem to JetBrains.
In the meantime, you can work around the problem by casting the return from uri:
String responses = ((WebClient.RequestHeadersSpec)webClient.get().uri(""))
.retrieve().bodyToMono(String.class).block()

How to enable 'ALLOW_NUMERIC_LEADING_ZEROS' feature to allow leading zeroes in JSON Request Body?

As per JSON specification, I am aware that leading zeroes are not allowed in integers in JSON. But as per Jackson documentation, there is a property in Jackson library i.e. ALLOW_NUMERIC_LEADING_ZEROS which when enabled, does not throw exceptions when leading zeroes are found.
I enabled the property ALLOW_NUMERIC_LEADING_ZEROS by setting following property and still I am getting error: Leading zeroes not allowed.
spring.jackson.parser.ALLOW_NUMERIC_LEADING_ZEROS=true
Relevant Logs:
Caused by: com.fasterxml.jackson.core.JsonParseException: Invalid numeric value: Leading zeroes not allowed
at [Source: (PushbackInputStream); line: 8, column: 17]
at com.fasterxml.jackson.core.JsonParser._constructError(JsonParser.java:1804) ~[jackson-core-2.9.4.jar:2.9.4]
at com.fasterxml.jackson.core.base.ParserMinimalBase._reportError(ParserMinimalBase.java:663) ~[jackson-core-2.9.4.jar:2.9.4]
at com.fasterxml.jackson.core.base.ParserMinimalBase.reportInvalidNumber(ParserMinimalBase.java:539) ~[jackson-core-2.9.4.jar:2.9.4]
at com.fasterxml.jackson.core.json.UTF8StreamJsonParser._verifyNoLeadingZeroes(UTF8StreamJsonParser.java:1489) ~[jackson-core-2.9.4.jar:2.9.4]
at com.fasterxml.jackson.core.json.UTF8StreamJsonParser._parsePosNumber(UTF8StreamJsonParser.java:1341) ~[jackson-core-2.9.4.jar:2.9.4]
at com.fasterxml.jackson.core.json.UTF8StreamJsonParser.nextFieldName(UTF8StreamJsonParser.java:1025) ~[jackson-core-2.9.4.jar:2.9.4]
at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:376) ~[jackson-databind-2.9.4.jar:2.9.4]
at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:159) ~[jackson-databind-2.9.4.jar:2.9.4]
at com.fasterxml.jackson.databind.deser.impl.MethodProperty.deserializeAndSet(MethodProperty.java:127) ~[jackson-databind-2.9.4.jar:2.9.4]
at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:369) ~[jackson-databind-2.9.4.jar:2.9.4]
at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:159) ~[jackson-databind-2.9.4.jar:2.9.4]
at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4001) ~[jackson-databind-2.9.4.jar:2.9.4]
at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3072) ~[jackson-databind-2.9.4.jar:2.9.4]
at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.readJavaType(AbstractJackson2HttpMessageConverter.java:235) ~[spring-web-5.0.4.RELEASE.jar:5.0.4.RELEASE]
... 63 more
I verified whether the property ALLOW_NUMERIC_LEADING_ZEROS has been enabled or not by executing following code:
#Autowired
private ObjectMapper objectMapper;
#PostMapping(path = "random_path", produces = MediaType.APPLICATION_JSON_VALUE, consumes = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Object> fun123( #RequestBody RandomClass obj) throws Exception {
log.info(" isEnabled = " + objectMapper.getFactory().isEnabled(JsonParser.Feature.ALLOW_NUMERIC_LEADING_ZEROS));
log.info(" isEnabled = " + objectMapper.isEnabled(JsonParser.Feature.ALLOW_NUMERIC_LEADING_ZEROS));
/*
When correct request is given i.e. no leading zeroes in json body , then this function is successfully executed and
output is true for above two statements => i.e. feature 'ALLOW_NUMERIC_LEADING_ZEROS' is enabled.
When leading zeroes are present in json request body, this function is not executed as an exception 'HttpMessageNotReadableException'
is generated with error message 'Invalid numeric value: Leading zeroes not allowed'
*/
....
}
As per code of UTF8StreamJsonParser.java , when this property is enabled the exception should not have occurred, but I am not sure why this is happening !! Any idea what can be the reason behind this ?
Relevant code from UTF8StreamJsonParser.java :
/**
* Method called when we have seen one zero, and want to ensure
* it is not followed by another
*/
private final int _verifyNoLeadingZeroes() throws IOException
{
// Ok to have plain "0"
if (_inputPtr >= _inputEnd && !_loadMore()) {
return INT_0;
}
int ch = _inputBuffer[_inputPtr] & 0xFF;
// if not followed by a number (probably '.'); return zero as is, to be included
if (ch < INT_0 || ch > INT_9) {
return INT_0;
}
// [JACKSON-358]: we may want to allow them, after all...
if (!isEnabled(Feature.ALLOW_NUMERIC_LEADING_ZEROS)) {
reportInvalidNumber("Leading zeroes not allowed");
}
...
}
Jackson Library Version Used : 2.9.4
Simply put following property on your application.properties file
spring.jackson.parser.allow-numeric-leading-zeros=true
You can set jackson as default converter by following property if not set default
spring.http.converters.preferred-json-mapper=jackson
This is most likely due to ObjectMapper that Spring endpoint uses being configured different from mapper being injected into field.
Why this is I can't say -- maybe Spring users list could help.
MappingJackson2HttpMessageConverter by default uses Jackson2ObjectMapperBuilder class to build new instance of ObjectMapper class. To override and use ObjectMapper from container we need to override JSON converter:
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
import java.util.List;
#Configuration
public class JacksonMvcConfiguration extends WebMvcConfigurationSupport {
#Autowired
private ObjectMapper objectMapper;
public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter() {
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
converter.setObjectMapper(objectMapper);
return converter;
}
#Override
protected void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(mappingJackson2HttpMessageConverter());
super.configureMessageConverters(converters);
}
}
See Customizing HttpMessageConverters with Spring Boot and Spring MVC. Since now you should be able to parse numbers with leading zeros.
Just a note for people that are having that issue and are looking for a newer working solution:
Import latest version of fasterxml jackson in maven (2.11.0 as of today):
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
<version>2.11.0</version>
</dependency>
Create the mapper object:
ObjectMapper objectMapper = new ObjectMapper();
Allow the leading zeros for numbers (the not deprecated version):
objectMapper.enable(JsonReadFeature.ALLOW_LEADING_ZEROS_FOR_NUMBERS.mappedFeature());
used imports:
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.core.json.JsonReadFeature;
Keep in mind this will trim the leading 0s. If you want to keep them then your json value shouldn't be a numeric.

Unable to generate the Spring rest docs using Cucumber

I am trying to test spring rest documentation for rest API for our services using spring cucumber jvm but end up with a null pointer exeception when I try to execute the scenario, as the framework is not able to intialize the Junit context.
Error Message:
java.lang.NullPointerException at
org.springframework.restdocs.ManualRestDocumentation.beforeO‌​peration(ManualRestD‌​ocumentation.java:90‌​) at
org.springframework.restdocs.JUnitRestDocumentation.beforeOp‌​eration(JUnitRestDoc‌​umentation.java:76)
Code:
private AppProperties props;
#Before("#rest") public void beforeScenario() {
JUnitRestDocumentation restDocumentation = new JUnitRestDocumentation( "target/generated-snippets" );
System.out.println( "jUnitRestDocumentation " +restDocumentation );
spec = new RequestSpecBuilder().addFilter( documentationConfiguration( restDocumentation ) ).build();
System.out.println( "\n spec init .. " +restDocumentation );
}
Step definition code:
#Given("^create a rest document for VHR API$")
public void create_a_rest_document_for_VHR_API() throws Throwable {
estAssured.given( spec )
.accept( "application/json" )
.filter( document( "vhrdocument" ) ) .when()
.get( props.getVhrrequesturl() + "/vhrData/{vehicleID}", "5VW4T7AU0FM029999" ) .then().log().all();
}
You aren't using JUnitRestDocumentation as it's intended to be used. It's designed to be used as a JUnit rule which means it should be a public field annotated with #Rule:
#Rule
public JUnitRestDocumentation restDocumentation = new JUnitRestDocumentation();
Being a rule means that JUnit will automatically call restDocumentation for each test, allowing Spring REST Docs to set up and tear down the test-specific context. The NullPointerException is occurring because restDocumentation hasn't been called in this way and, therefore, the context hasn't been set up.
You haven't described how you're using Cucumber, but if you're using it's JUnit runner you should be able to fix the problem by declaring restDocumentation as a #Rule-annotated field as shown above. If you're not using its JUnit runner, you may need to use Spring REST Docs' ManualRestDocumentation instead. The Spring REST Docs reference documentation contains a section that describes how to set up your tests when you're not using JUnit.
I had the same problem because I had multiple test class inheriting the class, in which I declared the JUnitRestDocumentation instance. My mistake was that I declared the rule using the #Rule annotation. I should have used #ClassRule and declared the instance as static.
#ClassRule
public static JUnitRestDocumentation restDocumentation = new JUnitRestDocumentation();
it happened with test SpockFramework, and i added to pom.xml:
<dependency>
<groupId>org.spockframework</groupId>
<artifactId>spock-junit4</artifactId>
<scope>test</scope>
</dependency>
I had the same symptoms when migrating from RestAssured 2.x to RestAssured 3.1.1.
The codebase had a way to setup RestAssured in order to avoid repetitive ceremony for every tests :
#Rule
public JUnitRestDocumentation restDocumentation = new JUnitRestDocumentation();
#Before
public void configure_rest_assured() {
RestAssured.port = springServerPort;
RestAssured.config = config().objectMapperConfig(
objectMapperConfig().jackson2ObjectMapperFactory((cls, charset) -> customObjectMapper)
)
...;
RestAssured.requestSpecification = new RequestSpecBuilder()
.addRequestSpecification(documentationConfiguration(docRule, ...))
...
.build();
}
This was working well, until I migrated to 3.x. The issue was that new RequestSpecBuilder() will append itself to the default static RestAssured.requestSpecification.
The first test passed, but when it finished the rule was disposed (the after part), when the second test started to ran, the Before method was chaining
the specification created for the first test (referencing the disposed rule used by the first test method)
the specification created for the second test (referencing the active rule for second test method)
And so on as new tests are ran.
But when the second test is run RestAssured invoke specification in order, e.g. the number 1, but since it was referencing a disposed rule (the beforeOperation was executed on a null context)
To fix that the code had to clear the previous specifications :
#Before
public void configure_rest_assured() {
RestAssured.port = springServerPort;
RestAssured.config = config().objectMapperConfig(
objectMapperConfig().jackson2ObjectMapperFactory((cls, charset) -> customObjectMapper)
)
...;
RestAssured.requestSpecification = null; // avoid the builder to acquire previous specs.
RestAssured.requestSpecification = new RequestSpecBuilder()
.addRequestSpecification(documentationConfiguration(docRule, ...))
...
.build();
}
For using cucumber-java-8 with spring rest docs and spring-security the following worked for me.
This is combining #AndyWilkison's answer from above but using the cucumber hooks instead of junit rules.
public class StepDefs implements En {
#Autowired
private WebApplicationContext context;
private MockMvc mockMvc;
private ManualRestDocumentation restDocumentation = new ManualRestDocumentation();
public StepDefs() {
BeforeStep((Scenario scenario) -> {
restDocumentation.beforeTest(AuthenticationStepDefs.class, scenario.getName());
mockMvc = MockMvcBuilders.webAppContextSetup(context).apply(springSecurity()).apply(documentationConfiguration(restDocumentation)).build();
});
AfterStep((Scenario scenario) -> {
restDocumentation.afterTest();
});
When("create a rest document for VHR API", () -> {
MvcResult result = mockMvc.perform(/*
your normal call here
*/).
.andDo(document("documentation")).
.andReturn();
}
}
}

Running cucumber-groovy features against a spring boot api

I've been attempting to get cucumber-groovy working with spring-boot, but it's not been going well. I get the error org.springframework.web.client.ResourceAccessException: I/O error on GET request for "http://localhost:8080/applicants": Connection refused; nested exception is java.net.ConnectException: Connection refused which seems to indicate that it's hitting the endpoint, but that the service isn't running.
I've read that I need to have a cucumber.xml file, but my project is not using any xml config, it's all annotations, so instead I've got this:
package support
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
#Configuration
#ComponentScan(basePackages = "com.base.package")
public class CucumberConfiguration {}
I've added it to the World, but this seems to be the wrong way of doing things (i.e. I don't know how to add an annotation on groovy step defs).
package support
import com.thing.app.Application
import org.junit.runner.RunWith
import org.springframework.boot.test.SpringApplicationContextLoader
import org.springframework.boot.test.WebIntegrationTest
import org.springframework.test.context.ContextConfiguration
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner
import org.springframework.test.context.web.WebAppConfiguration
import static cucumber.api.groovy.Hooks.*
//#RunWith(SpringJUnit4ClassRunner)
//#ContextConfiguration(classes = Application, loader = SpringApplicationContextLoader)
//#WebAppConfiguration
//#WebIntegrationTest
#ContextConfiguration(classes = CucumberConfiguration)
public class AbstractTest {
}
World() {
new AbstractTest()
}
Before() {}
After() {}
I left in my other annotations to kind of show what I've done so far. None of it has worked.
I've also tried setting up an AbstractDefs class as seen here https://github.com/jakehschwartz/spring-boot-cucumber-example/tree/master/src/test/java/demo, but that also hasn't worked, mostly because I'm not using the cucumber-java style of things, but instead the cucumber-groovy style, which doesn't use step definition classes.
Edit: Just discovered I was doing things wrong by having an env.groovy, I'm used to the ruby cucumber, so I'm having trouble finding all the little problems. Still am having the same issue though, I don't know how to execute in a Spring context.
You can instantiate Spring test context with io.cucumber.spring.SpringFactory and register adapter in World to allow groovy script has access to Spring beans:
env.groovy:
#ContextConfiguration(classes = TestConfiguration, loader = SpringBootContextLoader)
class CucumberContextConfiguration {
}
//adapter bypassing World properties to Spring context
class SpringFactoryWorldAdapter {
private final SpringFactory factory;
SpringFactoryWorldAdapter(SpringFactory factory) {
this.factory = factory;
}
#Override
Object getProperty(String s) {
return factory.testContextManager.getContext().getBean(s);
}
}
def factory; //Keep state to prevent repeated context initialization
World { args ->
if (factory == null) {
factory = new SpringFactory()
factory.addClass(CucumberContextConfiguration)
factory.start()
}
new SpringFactoryWorldAdapter(factory)
}

Resources