In my spring boot app, I want to write a web-test.
My application returns a list of strings. The test however produces a list with only one element (complete json as string).
My (minimal example) production code:
#SpringBootApplication
public class BackendApplication {
public static void main(String[] args) {
SpringApplication.run(BackendApplication.class, args);
}
}
#RestController
#RequestMapping("/allBoxes")
class StackOverFlowController {
#GetMapping
public List<String> getNamesOfAllBoxes() {
return List.of("Fruits", "Regional");
}
}
My test class:
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class StackOverFlowControllerTest {
#Autowired
WebTestClient webTestClient;
#Test
void whenListOfStringsEndpoint_thenExpectListOfStrings(){
// When
List<String> actual = webTestClient.get()
.uri("/allBoxes")
.exchange()
.expectStatus().is2xxSuccessful()
.expectBodyList(String.class)
.returnResult()
.getResponseBody();
// Then
Assertions.assertEquals(List.of("Fruits", "Regional"), actual);
}
}
My maven dependencies (spring boot 2.7.0 parent):
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
The test fails:
org.opentest4j.AssertionFailedError:
Expected :[Fruits, Regional]
Actual :[["Fruits","Regional"]]
However, if I access the production application via postman, I receive:
["Fruits","Regional"]
Why does the reactive WebTestClient not parse this json, but instead create an array with only one string? How can I tell it to parse the string and give me a list of strings (with my two items as elements) instead?
If you replace
.expectBodyList(String.class)
by
.expectBody(new ParameterizedTypeReference<List<String>>() {})
it works. Like this:
#Test
void whenListOfStringsEndpoint_thenExpectListOfStrings(){
// When
List<String> actual = webTestClient.get()
.uri("/allBoxes")
.exchange()
.expectStatus().is2xxSuccessful()
.expectBody(new ParameterizedTypeReference<List<String>>() {})
.returnResult()
.getResponseBody();
// Then
Assertions.assertEquals(List.of("Fruits", "Regional"), actual);
}
Check the default behaviour of Jackson2Decoder of Spring WebClient
https://docs.spring.io/spring-framework/docs/current/reference/html/web-reactive.html#webflux-codecs-jackson
For a multi-value publisher, WebClient by default collects the values with Flux#collectToList() and then serializes the resulting collection.
You will need to deserialize accordingly.
Related
I want to make scalar type it will report an error
Even I use extended-scalars, It did not work at all.
Version:
spring-boot: 2.7.0
<dependency>
<groupId>com.graphql-java-kickstart</groupId>
<artifactId>graphql-spring-boot-starter</artifactId>
<version>12.0.0</version>
</dependency>
<dependency>
<groupId>com.graphql-java-kickstart</groupId>
<artifactId>graphql-java-tools</artifactId>
<version>12.0.0</version>
</dependency>
<dependency>
<groupId>com.graphql-java</groupId>
<artifactId>graphiql-spring-boot-starter</artifactId>
<version>5.0.2</version>
</dependency>
Code
#Configuration
public class Conf {
#Bean
public GraphQLScalarType graphQLLong() {
return ExtendedScalars.GraphQLLong;
}
}
Caused by: SchemaProblem{errors=[There is no scalar implementation for the named 'Long' scalar type]}
Schema structure (a very simple one)
scalar Long
type Mutation {
}
yml config
So what config was I lost?
Include graphql-java-extended-scalars dependency:
https://github.com/graphql-java/graphql-java-extended-scalars
And make sure in your #Configuration class you are using:
#Bean
public graphql.schema.GraphQLScalarType extendedScalarLong() {
return graphql.scalars.ExtendedScalars.GraphQLLong;
}
Also, if you are defining your ExtendedScalar as a #Bean, then there's no need to specify it in graphql.extended-scalars property.
Use this :
#Bean
public RuntimeWiringConfigurer runtimeWiringConfigurer() {
return wiringBuilder -> wiringBuilder.scalar(ExtendedScalars.GraphQLLong);
}
Instead of :
#Bean
public GraphQLScalarType graphQLLong() {
return ExtendedScalars.GraphQLLong;
}
while migrating from junit4 to junit5, instead of
#Parameterized.Parameters
public static Collection input() {}
I have added before all the test methods
#ParameterizedTest
#MethodSource("input")
but I am getting the error as :
org.junit.jupiter.api.extension.ParameterResolutionException: No ParameterResolver registered for parameter
Any help appreciated!
Example of correct implementation of parameterized test:
//example with 2 parameters
#ParameterizedTest
#MethodSource("input")
void myTest(String input, String expectedResult) {
//test code
}
// Make sure to use the correct return type Stream<Arguments>
static Stream<Arguments> input() {
return Stream.of(
Arguments.of("hello", "hello"),
Arguments.of("bye", "bye")
//etc
);
}
Also make sure you are using a compatible version of junit jupiter:
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>${some.version}</version>
<scope>test</scope>
</dependency>
Also you need junit-vintage-engine dependency if you still have junit4 tests.
I'd like to write a rest service that accepts and produces XML or JSON based on the information from headers. To do so, I followed one of tutorials. The problem is that when I try to read fields of dto in Spring controller, they are all set to null.
For test purposes, I send in body a DTO and in controller I return it concatenating string Changed to two of its fields.
In body I send:
<?xml version="1.0" encoding="UTF-8"?>
<name>name</name>
<description>description</description>
However, I receive:
<Dto name="null Changed" description="null Changed"/>
I send request by Postman:
Here's my configuration:
Controller
#RestController
public class Controller {
#RequestMapping(value = "/endpoint", method = RequestMethod.POST,
consumes = {APPLICATION_JSON_VALUE, APPLICATION_XML_VALUE},
produces = {APPLICATION_JSON_VALUE, APPLICATION_XML_VALUE})
public Dto getAndReturnEntity(#RequestBody Dto dto) {
dto.setName(dto.getName() + " Changed");
dto.setDescription(dto.getDescription() + " Changed");
return dto;
}
}
DTO
#JacksonXmlRootElement
public class Dto {
#JacksonXmlProperty(isAttribute = true) // I also tried without it
private String name;
#JacksonXmlProperty(isAttribute = true)
private String description;
// getters and setters ommited for brevity
}
Configuration for Spring
#Configuration
#EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {
#Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
configurer.favorPathExtension(true).
favorParameter(false).
parameterName("mediaType").
ignoreAcceptHeader(false).
useJaf(false).
defaultContentType(MediaType.APPLICATION_JSON).
mediaType("xml", MediaType.APPLICATION_XML).
mediaType("json", MediaType.APPLICATION_JSON);
}
}
Relevant part of pom.xml
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
The input XML should be like this:
<Dto>
<name>abcd</name>
<description>desc</description>
</Dto>
In case you want the tag names be different than you can use either custom object mapper or add #JsonProperty.
I am following spring data rest from https://spring.io/guides/gs/accessing-data-rest/ and I am only using
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-rest</artifactId>
</dependency>
I would like to know how can I return all records (without pagination) but not using spring-boot-starter-web.I wants to keep my code as small as possible.
I tried following but it is not working
#RepositoryRestResource(collectionResourceRel = "people" , path = "people")
public interface PersonRepository extends PagingAndSortingRepository<Person, Long> {
List<Person> findAllByLastName(#Param("name") String name);
default List<Person> findAll(){
Pageable pageable = null;
return (List<Person>) this.findAll(pageable);
};
}
I mean if I have whole MVC, I can do it but I like to keep my code to minimum.
Spring Data REST is itself a Spring MVC application and is designed in
such a way that it should integrate with your existing Spring MVC
applications with little effort. An existing (or future) layer of
services can run alongside Spring Data REST with only minor additional
work.
If you are using current version of spring boot, there is no need to mark your repository with #RepositoryRestResource; also spring will auto-configure Spring Data Rest once it found the spring-data-rest dependency in your path, bellow you will find steps with minimum config :
In pom.xml :
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-rest</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
</dependency>
Define your Entity + Repository :
Order.java
#Entity(name = "SampleOrder")
#Data
public class Order {
#Id #GeneratedValue//
private Long id;
private String name;
}
OrderRepository.java
public interface OrderRepository extends CrudRepository<Order, Long> {
}
Application.java
#SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
Test your API :
curl http://localhost:8080
< HTTP/1.1 200 OK
< Content-Type: application/hal+json
{ "_links" : {
"orders" : {
"href" : "http://localhost:8080/orders"
}
}
}
As #abdelghani-roussi shows, you can use the CrudRepository instead of the PagingAndSortingRepository, e.g.:
public interface PersonRepository extends CrudRepository<Person, Long> {
List<Person> findAllByLastName(#Param("name") String name);
// don't need to define findAll(), it's defined by CrudRepository
}
and then the default findAll() method will return a List<Person> that isn't paged.
Note: as I mentioned in my comment, by including the dependency on spring-boot-starter-data-rest you are also pulling in the Web dependencies, so you can't avoid that.
To pass variables between steps I have the step methods belong to the same class, and use fields of the class for the passed information.
Here is an example as follows:
Feature: Demo
Scenario: Create user
Given User creation form management
When Create user with name "TEST"
Then User is created successfully
Java class with steps definitions:
public class CreateUserSteps {
private String userName;
#Given("^User creation form management$")
public void User_creation_form_management() throws Throwable {
// ...
}
#When("^Create user with name \"([^\"]*)\"$")
public void Create_user_with_name(String userName) throws Throwable {
//...
this.userName = userName;
}
#Then("^User is created successfully$")
public void User_is_created_successfully() throws Throwable {
// Assert if exists an user with name equals to this.userName
}
My question is if it is a good practice to share information between steps? Or would be better to define the feature as:
Then User with name "TEST" is created successfully
In order to share commonalities between steps you need to use a World. In Java it is not as clear as in Ruby.
Quoting the creator of Cucumber.
The purpose of a "World" is twofold:
Isolate state between scenarios.
Share data between step definitions and hooks within a scenario.
How this is implemented is language specific. For example, in ruby,
the implicit self variable inside a step definition points to the
current scenario's World object. This is by default an instance of
Object, but it can be anything you want if you use the World hook.
In Java, you have many (possibly connected) World objects.
The equivalent of the World in Cucumber-Java is all of the objects
with hook or stepdef annotations. In other words, any class with
methods annotated with #Before, #After, #Given and so on will be
instantiated exactly once for each scenario.
This achieves the first goal. To achieve the second goal you have two
approaches:
a) Use a single class for all of your step definitions and hooks
b) Use several classes divided by responsibility [1] and use dependency
injection [2] to connect them to each other.
Option a) quickly breaks down because your step definition code
becomes a mess. That's why people tend to use b).
[1] https://cucumber.io/docs/gherkin/step-organization/
[2] PicoContainer, Spring, Guice, Weld, OpenEJB, Needle
The available Dependency Injection modules are:
cucumber-picocontainer
cucumber-guice
cucumber-openejb
cucumber-spring
cucumber-weld
cucumber-needle
Original post here https://groups.google.com/forum/#!topic/cukes/8ugcVreXP0Y.
Hope this helps.
It's fine to share data between steps defined within a class using an instance variable. If you need to share data between steps in different classes you should look at the DI integrations (PicoContainer is the simplest).
In the example you show, I'd ask whether showing "TEST" in the scenario is necessary at all. The fact that the user is called TEST is an incidental detail and makes the scenario less readable. Why not generate a random name (or hard code something) in Create_user_with_name()?
In Pure java, I just use a Singleton object that gets created once and cleared after tests.
public class TestData_Singleton {
private static TestData_Singleton myself = new TestData_Singleton();
private TestData_Singleton(){ }
public static TestData_Singleton getInstance(){
if(myself == null){
myself = new TestData_Singleton();
}
return myself;
}
public void ClearTestData(){
myself = new TestData_Singleton();
}
I would say that there are reasons to share information between steps, but I don't think that's the case in this scenario. If you propagate the user name via the test steps then it's not really clear from the feature what's going on. I think it's better to specifically say in the scenario what is expected. I would probably do something like this:
Feature: Demo
Scenario: Create user
Given User creation form management
When Create user with name "TEST"
Then A user named "TEST" has been created
Then, your actual test steps might look something like:
#When("^Create user with name \"([^\"]*)\"$")
public void Create_user_with_name(String userName) throws Throwable {
userService.createUser(userName);
}
#Then("^A user named \"([^\"]*)\" has been created$")
public void User_is_created_successfully(String userName) throws Throwable {
assertNotNull(userService.getUser(userName));
}
Here my way: I define a custom Scenario-Scope with spring
every new scenario there will be a fresh context
Feature #Dummy
Scenario: zweites Scenario
When Eins
Then Zwei
1: Use spring
<properties>
<cucumber.version>1.2.5</cucumber.version>
<junit.version>4.12</junit.version>
</properties>
<!-- cucumber section -->
<dependency>
<groupId>info.cukes</groupId>
<artifactId>cucumber-java</artifactId>
<version>${cucumber.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>info.cukes</groupId>
<artifactId>cucumber-junit</artifactId>
<version>${cucumber.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>info.cukes</groupId>
<artifactId>cucumber-spring</artifactId>
<version>${cucumber.version}</version>
<scope>test</scope>
</dependency>
<!-- end cucumber section -->
<!-- spring-stuff -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>4.3.4.RELEASE</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.3.4.RELEASE</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>4.3.4.RELEASE</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>4.3.4.RELEASE</version>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>4.3.4.RELEASE</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.ws</groupId>
<artifactId>spring-ws-core</artifactId>
<version>2.4.0.RELEASE</version>
<scope>test</scope>
</dependency>
2: build custom scope class
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
#Component
#Scope(scopeName="scenario")
public class ScenarioContext {
public Scenario getScenario() {
return scenario;
}
public void setScenario(Scenario scenario) {
this.scenario = scenario;
}
public String shareMe;
}
3: usage in stepdef
#ContextConfiguration(classes = { CucumberConfiguration.class })
public class StepdefsAuskunft {
private static Logger logger = Logger.getLogger(StepdefsAuskunft.class.getName());
#Autowired
private ApplicationContext applicationContext;
// Inject service here : The impl-class need #Primary #Service
// #Autowired
// IAuskunftservice auskunftservice;
public ScenarioContext getScenarioContext() {
return (ScenarioContext) applicationContext.getBean(ScenarioContext.class);
}
#Before
public void before(Scenario scenario) {
ConfigurableListableBeanFactory beanFactory = ((GenericApplicationContext) applicationContext).getBeanFactory();
beanFactory.registerScope("scenario", new ScenarioScope());
ScenarioContext context = applicationContext.getBean(ScenarioContext.class);
context.setScenario(scenario);
logger.fine("Context für Scenario " + scenario.getName() + " erzeugt");
}
#After
public void after(Scenario scenario) {
ScenarioContext context = applicationContext.getBean(ScenarioContext.class);
logger.fine("Context für Scenario " + scenario.getName() + " gelöscht");
}
#When("^Eins$")
public void eins() throws Throwable {
System.out.println(getScenarioContext().getScenario().getName());
getScenarioContext().shareMe = "demo"
// you can save servicecall here
}
#Then("^Zwei$")
public void zwei() throws Throwable {
System.out.println(getScenarioContext().getScenario().getName());
System.out.println(getScenarioContext().shareMe);
// you can use last service call here
}
#Configuration
#ComponentScan(basePackages = "i.am.the.greatest.company.cucumber")
public class CucumberConfiguration {
}
the scope class
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.config.Scope;
public class ScenarioScope implements Scope {
private Map<String, Object> objectMap = Collections.synchronizedMap(new HashMap<String, Object>());
/** (non-Javadoc)
* #see org.springframework.beans.factory.config.Scope#get(java.lang.String, org.springframework.beans.factory.ObjectFactory)
*/
public Object get(String name, ObjectFactory<?> objectFactory) {
if (!objectMap.containsKey(name)) {
objectMap.put(name, objectFactory.getObject());
}
return objectMap.get(name);
}
/** (non-Javadoc)
* #see org.springframework.beans.factory.config.Scope#remove(java.lang.String)
*/
public Object remove(String name) {
return objectMap.remove(name);
}
/** (non-Javadoc)
* #see org.springframework.beans.factory.config.Scope#registerDestructionCallback(java.lang.String, java.lang.Runnable)
*/
public void registerDestructionCallback(String name, Runnable callback) {
// do nothing
}
/** (non-Javadoc)
* #see org.springframework.beans.factory.config.Scope#resolveContextualObject(java.lang.String)
*/
public Object resolveContextualObject(String key) {
return null;
}
/** (non-Javadoc)
* #see org.springframework.beans.factory.config.Scope#getConversationId()
*/
public String getConversationId() {
return "VolatileScope";
}
/**
* vaporize the beans
*/
public void vaporize() {
objectMap.clear();
}
}
Other option is to use ThreadLocal storage. Create a context map and add them to the map. Cucumber JVM runs all the steps in the same thread and you have access to that across all the steps. To make it easier, you can instantiate the storage in before hook and clear in after hook.
If you are using Serenity framework with cucumber you can use current session.
Serenity.getCurrentSession()
more about this feature in http://thucydides-webtests.com/2012/02/22/managing-state-between-steps/. (Serenity was called Thucydides before)