(Although, there are plenty of similar questions I was not able to find the solution among them).
Why #Transactional annotation with #SpringBootTest works and rolls back nicely when I use DAO directly, but does not work when I test with TestRestTemplate?
tl;dr
Entity
#Entity
public class ToDoItem {
#Id #GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#NotNull #Column(unique = true)
private String title;
public ToDoItem() {}
public ToDoItem(String title) { this.title = title;}
// getters, setters, etc.
Repository
public interface ToDoRepository extends CrudRepository<ToDoItem, Long> {}
Application
#EnableTransactionManagement
#SpringBootApplication
#RestController
#RequestMapping("/todos")
public class ToDoApp {
public static void main(String[] args) {
SpringApplication.run(ToDoApp.class, args);
}
#Autowired
private ToDoRepository toDoRepository;
#GetMapping
ResponseEntity<?> fetchAll() { return ok(toDoRepository.findAll()); }
#PostMapping()
ResponseEntity<?> createNew(#RequestBody ToDoItem toDoItem) {
try {
toDoRepository.save(toDoItem);
return noContent().build();
} catch (Exception e) {
return badRequest().body(e.getMessage());
}
}
}
Now I create two tests which do basically the same: store some to-do items in in-memory database and check if size has increased:
Repository test
#ExtendWith(SpringExtension.class)
#SpringBootTest
#Transactional
class ToDoRepoTests {
#Autowired
private ToDoRepository toDoRepository;
#Test
void when_adding_one_item_then_success() {
toDoRepository.save(new ToDoItem("Run tests"));
assertThat(toDoRepository.findAll()).hasSize(1);
}
#Test
void when_adding_two_items_then_success() {
toDoRepository.saveAll(List.of(
new ToDoItem("Run tests"), new ToDoItem("Deploy to prod")));
assertThat(toDoRepository.findAll()).hasSize(2);
}
}
Then I create similar test that does exactly the same thing but via REST API (This one does not work and fails with Unique index or primary key violation):
#ExtendWith(SpringExtension.class)
#SpringBootTest(classes = ToDoApp.class, webEnvironment = WebEnvironment.RANDOM_PORT)
#Transactional
class ToDoControllerTests {
#LocalServerPort
private int localServerPort;
private TestRestTemplate testRestTemplate;
#BeforeEach
void setUp() {
testRestTemplate = new TestRestTemplate(new RestTemplateBuilder()
.rootUri("http://localhost:" + localServerPort));
}
#Test
void when_adding_one_item_then_success() {
// when
createToDo(new ToDoItem("Walk the dog"));
var allItems = fetchAllTodos();
// then
assertThat(allItems).hasSize(1);
}
#Test
void when_adding_two_items_then_success() {
// when
createToDo(new ToDoItem("Walk the dog"));
createToDo(new ToDoItem("Clean the kitchen"));
var allItems = fetchAllTodos();
// then
assertThat(allItems).hasSize(2);
}
private void createToDo(ToDoItem entity) {
var headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
var response = testRestTemplate.postForEntity("/todos", new HttpEntity<>(entity, headers), String.class);
assertThat(response.getStatusCode()).isEqualTo(NO_CONTENT);
}
private List<ToDoItem> fetchAllTodos() {
return Arrays.asList(testRestTemplate.getForObject("/todos", ToDoItem[].class));
}
}
And here is my pom.xml:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.5.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>foo.bar</groupId>
<artifactId>todo-app</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>To-Do App</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.11</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.4.2</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>11</source>
<target>11</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
Please, help me to understand, what I am doing wrong.
When you call the API using the RESTTemplate it crosses the boundary of the local transaction management and becomes as distributed transaction. There is not mechanism available for spring to roll back that call to the rest API. Although in your example the target system is the same application, it need not be so. It could be an external application that is not aware of the transactional boundaries that you have set in your test.
Related
My goal of the Project :
I have to read csv file by using Spring Batch and extract the specific column information like (Column Name :"msdin") "msdin" can print it on console. But my application is showing failed to start the application.
Well it is asking me to configure the data Source.Why we need to configure the data source in case of spring batch if my requirement is to read the csv file and print it on console.
I tried to identify the issues but not able to resolve. Can anybody help me how to resolve this issues?
Domain Class
public class Customer implements Serializable {
private Long id_type;
private String id_number;
private String customer_name;
private String email_address;
private LocalDate birthday;
private String citizenship;
private String address;
private Long msisdn;
private LocalDate kyc_date;
private String kyc_level;
private String goalscore;
private String mobile_network;
public Long getId_type() {
return id_type;
}
public void setId_type(Long id_type) {
this.id_type = id_type;
}
public String getId_number() {
return id_number;
}
public void setId_number(String id_number) {
this.id_number = id_number;
}
public String getCustomer_name() {
return customer_name;
}
public void setCustomer_name(String customer_name) {
this.customer_name = customer_name;
}
public String getEmail_address() {
return email_address;
}
public void setEmail_address(String email_address) {
this.email_address = email_address;
}
public LocalDate getBirthday() {
return birthday;
}
public void setBirthday(LocalDate birthday) {
this.birthday = birthday;
}
public String getCitizenship() {
return citizenship;
}
public void setCitizenship(String citizenship) {
this.citizenship = citizenship;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public Long getMsisdn() {
return msisdn;
}
public void setMsisdn(Long msisdn) {
this.msisdn = msisdn;
}
public LocalDate getKyc_date() {
return kyc_date;
}
public void setKyc_date(LocalDate kyc_date) {
this.kyc_date = kyc_date;
}
public String getKyc_level() {
return kyc_level;
}
public void setKyc_level(String kyc_level) {
this.kyc_level = kyc_level;
}
public String getGoalscore() {
return goalscore;
}
public void setGoalscore(String goalscore) {
this.goalscore = goalscore;
}
public String getMobile_network() {
return mobile_network;
}
public void setMobile_network(String mobile_network) {
this.mobile_network = mobile_network;
}
}
BatchConfiguration class
#Configuration
#EnableBatchProcessing
public class BatchConfiguration {
#Autowired
public JobBuilderFactory jobBuilderFactory;
#Autowired
public StepBuilderFactory stepBuilderFactory;
#Value("classPath:/data/gcash.csv")
private Resource inputResource;
public ItemReader<Customer> itemReader() {
FlatFileItemReader<Customer> customerItemReader = new FlatFileItemReader<>();
customerItemReader.setLineMapper(linemapper());
customerItemReader.setLinesToSkip(1);
customerItemReader.setResource(inputResource);
return customerItemReader;
}
#Bean
public LineMapper<Customer> linemapper() {
DefaultLineMapper<Customer> linemapper = new DefaultLineMapper<>();
final DelimitedLineTokenizer tokenizer = new DelimitedLineTokenizer();
tokenizer.setDelimiter(";");
tokenizer.setStrict(false);
tokenizer.setNames(new String[] { "id_type", "id_number", "customer_name", "email_address", "birthday",
"citizenship", "address", "msisdn", "kyc_date", "kyc_level", "goalscore", "mobile_network" });
linemapper.setFieldSetMapper(new BeanWrapperFieldSetMapper<Customer>() {
{
setTargetType(Customer.class);
}
});
return linemapper;
}
}
Error Stack
Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled.
2021-04-27 11:05:46.235 ERROR 22368 --- [ restartedMain] o.s.b.d.LoggingFailureAnalysisReporter :
***************************
APPLICATION FAILED TO START
***************************
Description:
Failed to configure a DataSource: 'url' attribute is not specified and no embedded datasource could be configured.
Reason: Failed to determine a suitable driver class
Action:
Consider the following:
If you want an embedded database (H2, HSQL or Derby), please put it on the classpath.
If you have database settings to be loaded from a particular profile you may need to activate it (no profiles are currently active).
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.3</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>
<groupId>com.gcash.milo</groupId>
<artifactId>GCash_Milo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>GCash_Milo</name>
<description>Developing Milo project for GCash banking application.
</description>
<properties>
<java.version>8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/commons-io/commons-io -->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.5</version>
</dependency>
<dependency>
<groupId>com.opencsv</groupId>
<artifactId>opencsv</artifactId>
<version>4.0</version>
</dependency>
<dependency>
<groupId>com.jcraft</groupId>
<artifactId>jsch</artifactId>
<version>0.1.54</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
<version>2.10</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-integration</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-batch</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-sftp</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.19.1</version>
<configuration>
<testFailureIgnore>true</testFailureIgnore>
</configuration>
</plugin>
</plugins>
</build>
</project>
in your spring boot application class, add the following snippet to #SpringBootApplication annotation:
#SpringBootApplication(exclude = {DataSourceAutoConfiguration.class })
#SpringBootApplcation annotation uses #EnableAutoConfiguration, which expects a datasource to be configured
I am creating a Spring Boot based Micro Service. This service will call a external service. I want to create Stub for that service to do my integration testing.
I have following configuration. But for some reason my while running my test class Stub is not properly created due to which my test is failing.
Test class
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
properties = {"server.context-path=/app", "app.baseUrl=http://restapi-2.herokuapp.com"})
#AutoConfigureWireMock
#AutoConfigureMockMvc
class Restapi1ApplicationTests {
#Autowired
private ApiService apiService;
#Autowired
private RestTemplate restTemplate;
#Autowired
private ObjectMapper objectMapper;
#Autowired
private MockMvc mockMvc;
#Test
void contextLoads() {
}
#Test
void getMessage() throws Exception {
MockRestServiceServer mockRestServiceServer = WireMockRestServiceServer.with(this.restTemplate)
.baseUrl("http://restapi-2.herokuapp.com")
.stubs("classpath:/stubs/companyresponse.json").build();
CompanyDetail companyDetail = new CompanyDetail();
companyDetail.setCompanyName("Test");
mockMvc.perform(MockMvcRequestBuilders.get("/api/v1/messages"))
.andExpect(status().isOk())
//.andExpect(MockMvcResultMatchers.content().contentType(MediaType.APPLICATION_JSON))
.andExpect(MockMvcResultMatchers.jsonPath("$.companyName").value("Test"));
//.andExpect(MockMvcResultMatchers.content().json(objectMapper.writeValueAsString(companyDetail)));
}
}
#RestController
public class Api_1_Controller {
private final ApiService apiService;
public Api_1_Controller(ApiService apiService) {
this.apiService = apiService;
}
#GetMapping(path = "/api/v1/messages")
public CompanyDetail getMessage(){
CompanyDetail companyDetail = apiService.getMessageFromApi();
return companyDetail;
}
}
#Service
public class ApiService {
private RestTemplate restTemplate;
public ApiService(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
public CompanyDetail getMessageFromApi(){
CompanyDetail companyDetail = null;
try{
companyDetail = restTemplate.getForEntity("http://restapi-2.herokuapp.com/companies",CompanyDetail.class).getBody();
}catch(Exception exception){
exception.printStackTrace();
throw exception;
}
return companyDetail;
}
}
#JsonInclude(value = JsonInclude.Include.NON_NULL)
public class CompanyDetail {
private String companyName;
//Getter and Setters
}
companyresponse.json is in below path
test/resources/stubs
{
"request": {
"urlPath": "/companies",
"method": "GET"
},
"response": {
"status": 200,
"jsonBody": {"companyName" : "Test"},
"headers": {
"Content-Type": "application/json"
}
}
}
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.4</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.heroku</groupId>
<artifactId>restapinew-1</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>restapinew-1</name>
<description>restapi-1 project for Spring Boot</description>
<properties>
<java.version>11</java.version>
<spring-cloud.version>2020.0.2</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-contract-stub-runner</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<imageName>restapinew-1</imageName>
</configuration>
</plugin>
</plugins>
</build>
</project>
When Running this test case getting Response body as null
MockHttpServletResponse:
Status = 200
Error message = null
Headers = []
Content type = null
Body =
Forwarded URL = null
Redirected URL = null
Cookies = []
java.lang.AssertionError: No value at JSON path "$.companyName"
at org.springframework.test.util.JsonPathExpectationsHelper.evaluateJsonPath(JsonPathExpectationsHelper.java:304)
at org.springframework.test.util.JsonPathExpectationsHelper.assertValue(JsonPathExpectationsHelper.java:99)
Instead of JsonBody use body attribute, enclose your response (json response) in double quotes it may resolve the issue.
I have implemented a spring boot web app using spring security and Keycloak to authenticate users based on this tutorial. I used maven, spring boot 2.2.2 and Keycloak 8.0.1. All things works correctly except a problem in single sign-out. When I open the secured path of spring boot app in a tab of my browser (http://localhost:8080/books) and Keycloak account page (http://localhost:8180/auth/realms/{realm_name}/account) in another tab and login with one of the users in one of them, the other tab will aware of the login and after reload the page, that page will also be authenticated. But the problem is where, when both tabs are logged in and first I log out from account page, and reload spring boot app, the user remains active and the app does not aware of the log out action in other tabs. How can I handle this problem?
my project resource tree is like image below:
Project resource tree
This is my pom.xml :
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.2.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.sso</groupId>
<artifactId>demoapp</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demoapp</name>
<description>Demo project for Spring Boot</description>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>13</java.version>
<keycloak.version>8.0.1</keycloak.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</dependency>
<!-- Keycloak Adapter -->
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-test-helper</artifactId>
<version>${keycloak.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.keycloak.bom</groupId>
<artifactId>keycloak-adapter-bom</artifactId>
<version>${keycloak.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
This is SecurityConfig.java :
#KeycloakConfiguration
public class SecurityConfig extends KeycloakWebSecurityConfigurerAdapter {
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) {
SimpleAuthorityMapper grantedAuthorityMapper = new SimpleAuthorityMapper();
grantedAuthorityMapper.setPrefix("ROLE_");
KeycloakAuthenticationProvider keycloakAuthenticationProvider = keycloakAuthenticationProvider();
keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(grantedAuthorityMapper);
auth.authenticationProvider(keycloakAuthenticationProvider);
}
/**
* Defines the session authentication strategy.
*/
#Bean
#Override
protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
return new RegisterSessionAuthenticationStrategy(new SessionRegistryImpl());
}
/**
* Define an HttpSessionManager bean only if missing.
*/
#Bean
#Override
#ConditionalOnMissingBean(HttpSessionManager.class)
protected HttpSessionManager httpSessionManager() {
return new HttpSessionManager();
}
/**
* Define security constraints for the application resources.
*/
#Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
http
.authorizeRequests()
.antMatchers("/books").authenticated()
.antMatchers("/manager").hasRole("admin")
.anyRequest().permitAll();
}
#Bean
public ServletListenerRegistrationBean<HttpSessionEventPublisher> httpSessionEventPublisher() {
return new ServletListenerRegistrationBean<HttpSessionEventPublisher>(new HttpSessionEventPublisher());
}
}
This is LibraryController.java :
#Controller
public class LibraryController {
private final HttpServletRequest request;
private final BookRepository bookRepository;
#Autowired
public LibraryController(HttpServletRequest request, BookRepository bookRepository) {
this.request = request;
this.bookRepository = bookRepository;
}
#GetMapping(value = "/")
public String getHome() {
return "index";
}
#GetMapping(value = "/books")
public String getBooks(Model model) {
configCommonAttributes(model);
model.addAttribute("books", bookRepository.readAll());
return "books";
}
#GetMapping(value = "/manager")
public String getManager(Model model) {
configCommonAttributes(model);
model.addAttribute("books", bookRepository.readAll());
return "manager";
}
#GetMapping(value = "/logout")
public String logout() throws ServletException {
request.logout();
return "redirect:/";
}
private void configCommonAttributes(Model model) {
model.addAttribute("firstname", getKeycloakSecurityContext().getIdToken().getGivenName());
model.addAttribute("lastname", getKeycloakSecurityContext().getIdToken().getFamilyName());
model.addAttribute("email", getKeycloakSecurityContext().getIdToken().getEmail());
}
/**
* The KeycloakSecurityContext provides access to several pieces of information
* contained in the security token, such as user profile information.
*/
private KeycloakSecurityContext getKeycloakSecurityContext() {
return (KeycloakSecurityContext) request.getAttribute(KeycloakSecurityContext.class.getName());
}
}
and below is my DemoappApplication.java :
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
#SpringBootApplication
public class DemoappApplication {
public static void main(String[] args) {
SpringApplication.run(DemoappApplication.class, args);
}
}
The problem was just solved by setting the base URL of my spring boot application as Admin-URL in the corresponding client configuration page of Keycloak server (admin console). Now, the spring boot app is aware of user log out from other apps.
I created a Spring Boot 2 application with Junit 4 and Mockito. When I test some method. there will be an exception like this :
java.lang.NullPointerException
at com.xxx.service.SurveyServiceTest.getSurveyList(SurveyServiceTest.java:41)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.mockito.internal.runners.DefaultInternalRunner$1.run(DefaultInternalRunner.java:79)
at org.mockito.internal.runners.DefaultInternalRunner.run(DefaultInternalRunner.java:85)
at org.mockito.internal.runners.StrictRunner.run(StrictRunner.java:39)
at org.mockito.junit.MockitoJUnitRunner.run(MockitoJUnitRunner.java:163)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
Here is my test code
#RunWith(MockitoJUnitRunner.class)
public class SurveyServiceTest {
#MockBean
SurveyRepository repostory;
#InjectMocks
SurveyService service;
#Test
public void getSurveyList() {
when(repostory.findAll()).thenReturn(Arrays.asList( new Survey(new Long(1),101,"Test1"),
new Survey(new Long(2),102,"Test2") ));
assertTrue(service.getSurveyList().size() >0);
}
}
SuveryService.java
#Service
public class SurveyService {
#Autowired
private SurveyRepository repostory;
public List<Survey> getSurveyList() {
return repostory.findAll();
}
public Optional<Survey> getSurveyById() {
return repostory.findById((long) 1);
}
public Survey add() {
Survey survey = new Survey();
survey.setSurveyID(1);
survey.setSurveyContent("ddddd");
return repostory.save(survey);
}
public Survey update() {
Survey survey = new Survey();
survey.setSurveyID(1);
survey.setSurveyContent("gggg1");
return repostory.save(survey);
}
public void delete() {
repostory.deleteById((long) 1);
}
public List<Survey> findBySurveyContent() {
return repostory.findBySurveyContent("gggg1");
}
public int updateBySurveyId(){
return repostory.updateBySurveyId("hhhhhh", 1);
}
}
SurveyRepository.java
public interface SurveyRepository extends JpaRepository<Survey, Long> {
public List<Survey> findBySurveyContent(String surveyContent);
#Query(value = "update XXX_DATA.SURVEYS set SURVEYCONTENT=? where SURVEYID=?",nativeQuery = true)
#Modifying
#Transactional
public int updateBySurveyId(String surveyContent,int surveyId);
}
Survey.java
#Entity
#Table(name="SURVEYS", schema="XXX_DATA")
public class Survey{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID")
private Long id;
#Column(name = "SURVEYID")
private Integer surveyID;
#Column(name = "SURVEYCONTENT")
private String surveyContent;
public Survey(){
}
public Survey(Long id,Integer surveyID,String surveyContent){
this.id = id;
this.surveyID = surveyID;
this.surveyContent = surveyContent;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Integer getSurveyID() {
return surveyID;
}
public void setSurveyID(Integer surveyID) {
this.surveyID = surveyID;
}
public String getSurveyContent() {
return surveyContent;
}
public void setSurveyContent(String surveyContent) {
this.surveyContent = surveyContent;
}
}
POM.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.xxx.test</groupId>
<artifactId>xxx-service</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>war</packaging>
<name>xxx-service</name>
<description>XXX Service</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.1.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<repositories>
<repository>
<id>maven-repository</id>
<url>file:///${project.basedir}/maven-repository</url>
</repository>
<repository>
<id>com.ibm.db2.jcc</id>
<url>https://artifacts.alfresco.com/nexus/content/repositories/public/</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>com.ibm.db2.jcc</groupId>
<artifactId>db2jcc4</artifactId>
<version>10.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.ibm.watson.developer_cloud</groupId>
<artifactId>java-sdk</artifactId>
<version>5.1.1</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</dependency>
<dependency>
<groupId>com.ibm.swat.password</groupId>
<artifactId>cwa2</artifactId>
<version>2.3.5.3</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>3.15</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml-schemas</artifactId>
<version>3.15</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
</plugins>
</build>
I referenced this article Mockito NullPointerException, but the issue still existed.
Any help will be appcirated.
#MockBean is a Spring annotation, which only has an effect if you create a Spring boot integration test, run with the SpringRunner.
You have a unit test here, run with MockitoJUnitRunner, and you just want Mockito to create your mock repository. The correct annotation is #Mock.
You sould do that :
#RunWith(MockitoJUnitRunner.class)
public class SurveyServiceTest {
#Mock// and not MockBean
SurveyRepository repostory;
#InjectMocks
SurveyService service;
#Test
public void getSurveyList() {
when(repostory.findAll()).thenReturn(Arrays.asList( new Survey(new Long(1),101,"Test1"),
new Survey(new Long(2),102,"Test2") ));
assertTrue(service.getSurveyList().size() >0);
}
}
To extend the above answers, Since #MockBean is a spring annotation, you can use the below code as an alternative. #MockBean is used to add mock objects to the Spring application context. The mock will replace any existing bean of the same type in the application context. So it is a kind of functionality of both #Mock as well as #InjectMock. Also if you not following JUnit 5 you require to replace #RunWith(SpringRunner.class) with #ExtendWith(SpringExtention.class)
#RunWith(SpringRunner.class)
public class SurveyServiceTest {
#MockBean
SurveyRepository repostory;
#Test
public void getSurveyList() {
when(repostory.findAll()).thenReturn(Arrays.asList( new Survey(new
Long(1),101,"Test1"),
new Survey(new Long(2),102,"Test2") ));
assertTrue(service.getSurveyList().size() >0);
}
}
I am new with spring boot, is very simple my question but I canĀ“t find where is the problem.
I have a basic #GetMapping and #DeleteMapping
#RestController
public class MyController
{
private MyServiceImpl myService;
#Autowired
public MyController(MyServiceImpl myService)
{
this.myService = myService;
}
#GetMapping("/test")
public List<Scan> getTestData(#RequestParam(value = "test_id", required = true) String test_id) {
return myService.findByTestId( test_id );
}
#DeleteMapping("/test")
public int deleteData(#RequestParam(value = "test_id", required = true) String test_id){
return myService.deleteTest( test_id );
}
#DeleteMapping("/test2")
public String deleteArticle() {
return "Test";
}
}
I am testing with Postman, the Get request is working, but the delete request is not. Even when I debug the spring boot application. The GetMapping is called, whereas the delete is not "reacting" /called
Maybe is not enough information, but I even tested #DeleteMapping("/test2") is not doing anything, I mean, I get no response, and in Postman it stays "loading..."
Any suggestions?
My complete POM
http://maven.apache.org/xsd/maven-4.0.0.xsd">
4.0.0
<parent>
<groupId>com.ttt.pdt</groupId>
<artifactId>pdt-base</artifactId>
<version>2.0-SNAPSHOT</version>
</parent>
<artifactId>pdt-service</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.4.0</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.4.0</version>
</dependency>
</dependencies>
<build>
<finalName>${project.artifactId}</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
The parent (pdt-base) has
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.3.RELEASE</version>
</parent>
MyServiceImpl
#Service
public class MyServiceImpl
{
private ScnDao scnDao;
#Autowired
public ScanServiceImpl(ScnDao scnDao){
this.scnDao = scnDao;
}
public int deleteTest( String test_id )
{
return scnDao.deleteTest( test_id );
}
}
public interface ScanDao
{
int deleteTest( String test_id )
}
#Repository
public class ScnPostgres implements ScnDao
{
private JdbcTemplate jdbcTemplate;
#Autowired
public ScnPostgres( JdbcTemplate jdbcTemplate )
{
this.jdbcTemplate = jdbcTemplate;
}
public int deleteTest( String test_id )
{
String SQL = "DELETE FROM scn WHERE test_id = ?";
return jdbcTemplate.update( SQL, new String[] { test_id } );
}
}
As in SPR comments described SPR-16874:
it does not support DELETE currently. It would be a trivial change to
make. There is suggested a workaround:
#DeleteMapping(value = "/greeting")
public Greeting handle(#RequestBody MultiValueMap<String, String> params) {
return new Greeting(counter.incrementAndGet(),
String.format(template, params));
}
As you can see the issue has been opened in the spring boot tracker.