I am trying to follow the example in https://jersey.java.net/documentation/latest/bean-validation.html#d0e13678, esp. section 18.4.3. I want to disallow json where Foo.number is missing. But my test returns status 200. Here is the full text of my test (I do have the jersey-bean-validation artifact in my pom):
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Application;
import javax.ws.rs.core.Response;
import javax.validation.Valid;
import javax.validation.constraints.NotNull;
import org.glassfish.jersey.client.ClientConfig;
import org.glassfish.jersey.jackson.JacksonFeature;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.test.JerseyTest;
import org.junit.Test;
import static org.junit.Assert.assertTrue;
public class DeleteMe extends JerseyTest {
#Path("/")
public static class Resource {
#POST
#Produces("application/json")
public Foo post(#Valid Foo foo) {
return foo;
}
}
public static class Foo {
#NotNull
private Integer number;
public void setNumber(final Integer number) {
this.number = number;
}
public Integer getNumber() {
return number;
}
}
#Override
protected Application configure() {
return new ResourceConfig(Resource.class)
.register(JacksonFeature.class);
}
#Override
protected void configureClient(final ClientConfig config) {
config.register(JacksonFeature.class);
}
#Test
public void testEntityFail() throws Exception {
Foo foo = new Foo();
Response response = target().request().post(javax.ws.rs.client.Entity.json(foo));
//I get 200 here...but foo is no good
assertTrue("Status "+response.getStatus(),response.getStatus() == 400);
}
}
PEBKAC -- I will leave the question up in case someone is getting started with validation and needs a dumbed-down "Hello World" test
Related
I'm using spring-boot 2.7.4 and spring-cloud-dependencies 2021.0.4.
I haven't found any solution in spring documentation for add trustedTypes in BatchMessagingMessageConverter. I'm using kafka for read messages in batch-mode. If I insert a custom header (my own class) when the consumer read the header return a DefaultKafkaHeaderMapper$NonTrustedHeaderType and not my class.
I have in my configuration this key to activate batch mode:
spring.cloud.stream.bindings.nameBind-in-0.consumer.batch-mode=true
I tried in debug to add to headerMapper in BatchMessagingMessageConverter the package of my class and all works fine. There is a way to specify my package in configuration?
I followed the documentation https://docs.spring.io/spring-cloud-stream/docs/3.2.5/reference/html/spring-cloud-stream-binder-kafka.html#kafka-binder-properties, I created a bean like this:
#Bean("kafkaHeaderMapperCustom")
KafkaHeaderMapper getKafkaHeaderMapperCustom() {
var defKHM = new DefaultKafkaHeaderMapper();
defKHM.addTrustedPackages("*");
return defKHM;
}
Specified to key spring.cloud.stream.kafka.binder.headerMapperBeanName in configuration but doesn't work, I suppose that configuration is valid for not batch context?
I tried also these properties:
spring.kafka.consumer.properties.spring.json.trusted.packages
spring.json.trusted.packages
EDIT - Add example:
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.stream.function.StreamBridge;
import org.springframework.context.annotation.Bean;
import org.springframework.kafka.support.DefaultKafkaHeaderMapper;
import org.springframework.kafka.support.KafkaHeaderMapper;
import org.springframework.kafka.support.KafkaHeaders;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHeaders;
import org.springframework.messaging.support.MessageBuilder;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
#SpringBootApplication
public class Application {
public static final String HEADER_KEY = "CUSTOM_HEADER_KEY";
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
#Bean
public ApplicationRunner runner(StreamBridge streamBridge) {
return args -> {
var headers = new MessageHeaders(Map.of(HEADER_KEY, new CustomHeaderClass("field1Value", LocalDate.now())));
headers.get(KafkaHeaders.BATCH_CONVERTED_HEADERS);
var message = MessageBuilder.createMessage(new ExampleBrokenHeaderEntity("randomValue", "randomName"), headers);
streamBridge.send("stackoverflow-example", message);
};
}
#Bean
public Consumer<Message<List<ExampleBrokenHeaderEntity>>> readFromKafkaBatchMode() {
return messages -> {
var brokenHeader = ((ArrayList<Map<String, Object>>) messages.getHeaders().get(KafkaHeaders.BATCH_CONVERTED_HEADERS)).get(0).get(HEADER_KEY);
System.out.println("BATCH - Class header: " + (brokenHeader != null ? brokenHeader.getClass() : null));
};
}
#Bean
public Consumer<Message<ExampleBrokenHeaderEntity>> readFromKafkaNoBatchMode() {
return messages -> {
var brokenHeader = messages.getHeaders().get(HEADER_KEY);
System.out.println("NO_BATCH - Class header: " + (brokenHeader != null ? brokenHeader.getClass() : null));
};
}
#Bean("kafkaHeaderMapperCustom")
public KafkaHeaderMapper getKafkaHeaderMapperBatchMode() {
var kafkaHeaderMapperCustom = new DefaultKafkaHeaderMapper();
kafkaHeaderMapperCustom.addTrustedPackages("*");
return kafkaHeaderMapperCustom;
}
}
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDate;
#Data
#NoArgsConstructor
#AllArgsConstructor
public class CustomHeaderClass {
private String field1;
private LocalDate field2;
}
import lombok.AllArgsConstructor;
import lombok.Data;
#Data
#AllArgsConstructor
public final class ExampleBrokenHeaderEntity {
private String type;
private String name;
}
spring.cloud.stream.kafka.binder.brokers=x.x.x.x:xxxx
spring.cloud.function.definition=readFromKafkaNoBatchMode;readFromKafkaBatchMode
spring.cloud.stream.bindings.readFromKafkaBatchMode-in-0.destination=stackoverflow-example
spring.cloud.stream.bindings.readFromKafkaBatchMode-in-0.group=readFromKafkaBatchMode
spring.cloud.stream.bindings.readFromKafkaBatchMode-in-0.consumer.batch-mode=true
spring.cloud.stream.bindings.readFromKafkaNoBatchMode-in-0.destination=stackoverflow-example
spring.cloud.stream.bindings.readFromKafkaNoBatchMode-in-0.group=readFromKafkaNoBatchMode
spring.cloud.stream.kafka.binder.headerMapperBeanName=kafkaHeaderMapperCustom
The output of example is:
NO_BATCH - Class header: class com.example.kafka.header.types.CustomHeaderClass
BATCH - Class header: class org.springframework.kafka.support.DefaultKafkaHeaderMapper$NonTrustedHeaderType
It's a bug; the binder only sets the custom header mapper on a record converter:
private MessageConverter getMessageConverter(
final ExtendedConsumerProperties<KafkaConsumerProperties> extendedConsumerProperties) {
MessageConverter messageConverter = BindingUtils.getConsumerMessageConverter(getApplicationContext(),
extendedConsumerProperties, this.configurationProperties);
if (messageConverter instanceof MessagingMessageConverter) {
((MessagingMessageConverter) messageConverter).setHeaderMapper(getHeaderMapper(extendedConsumerProperties));
}
return messageConverter;
}
There should be similar code for when the converter is a BatchMessagingMessageConverter.
The work around is to define a custom message converter for the batch consumer:
#Bean("batchConverter")
BatchMessageConverter batchConverter(KafkaHeaderMapper kafkaHeaderMapperCustom) {
BatchMessagingMessageConverter batchConv = new BatchMessagingMessageConverter();
batchConv.setHeaderMapper(kafkaHeaderMapperCustom);
return batchConv;
}
spring.cloud.stream.kafka.bindings.readFromKafkaBatchMode-in-0.consumer.converter-bean-name=batchConverter
NO_BATCH - Class header: class com.example.demo.So74294156Application$CustomHeaderClass
BATCH - Class header: class com.example.demo.So74294156Application$CustomHeaderClass
Please open an issue against Spring Cloud Stream, referencing this question/answer.
We need an example on how to test ReactiveKafkaConsumerTemplate and ReactiveKafkaProducerTemplate with an embedded-kafka-broker. Thanks.
CORRECT CODE IS HERE AFTR DISCUSSION
You can have your custom de-serializer accordingly to use custom ReactiveKafkaConsumerTemplate
Custom Serialiser:
import org.apache.kafka.common.serialization.Serializer;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
public class EmployeeSerializer implements Serializer<Employee> {
#Override
public byte[] serialize(String topic, Employee data) {
byte[] rb = null;
ObjectMapper mapper = new ObjectMapper();
try {
rb = mapper.writeValueAsString(data).getBytes();
} catch (JsonProcessingException e) {
e.printStackTrace();
}
return rb;
}
}
Use it part of embedded-kfka-reactive test:
import java.util.Map;
import org.apache.kafka.clients.producer.ProducerConfig;
import org.apache.kafka.clients.producer.ProducerRecord;
import org.apache.kafka.connect.json.JsonSerializer;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.kafka.core.reactive.ReactiveKafkaProducerTemplate;
import org.springframework.kafka.support.converter.MessagingMessageConverter;
import org.springframework.kafka.test.condition.EmbeddedKafkaCondition;
import org.springframework.kafka.test.context.EmbeddedKafka;
import org.springframework.kafka.test.utils.KafkaTestUtils;
import reactor.kafka.sender.SenderOptions;
import reactor.kafka.sender.SenderRecord;
import reactor.test.StepVerifier;
#EmbeddedKafka(topics = EmbeddedKafkareactiveTest.REACTIVE_INT_KEY_TOPIC,
brokerProperties = { "transaction.state.log.replication.factor=1", "transaction.state.log.min.isr=1" })
public class EmbeddedKafkareactiveTest {
public static final String REACTIVE_INT_KEY_TOPIC = "reactive_int_key_topic";
private static final Integer DEFAULT_KEY = 1;
private static final String DEFAULT_VERIFY_TIMEOUT = null;
private ReactiveKafkaProducerTemplate<Integer, Employee> reactiveKafkaProducerTemplate;
#BeforeEach
public void setUp() {
reactiveKafkaProducerTemplate = new ReactiveKafkaProducerTemplate<>(setupSenderOptionsWithDefaultTopic(),
new MessagingMessageConverter());
}
private SenderOptions<Integer, Employee> setupSenderOptionsWithDefaultTopic() {
Map<String, Object> senderProps = KafkaTestUtils
.producerProps(EmbeddedKafkaCondition.getBroker().getBrokersAsString());
SenderOptions<Integer, Employee> senderOptions = SenderOptions.create(senderProps);
senderOptions = senderOptions.producerProperty(ProducerConfig.TRANSACTIONAL_ID_CONFIG, "reactive.transaction")
.producerProperty(ProducerConfig.ENABLE_IDEMPOTENCE_CONFIG, true)
.producerProperty(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, JsonSerializer.class.getName())
;
return senderOptions;
}
#Test
public void test_When_Publish() {
Employee employee = new Employee();
ProducerRecord<Integer, Employee> producerRecord = new ProducerRecord<Integer, Employee>(REACTIVE_INT_KEY_TOPIC, DEFAULT_KEY, employee);
StepVerifier.create(reactiveKafkaProducerTemplate.send(producerRecord)
.then())
.expectComplete()
.verify();
}
#AfterEach
public void tearDown() {
reactiveKafkaProducerTemplate.close();
}
}
The tests in the framework use an embedded kafka broker.
https://github.com/spring-projects/spring-kafka/tree/main/spring-kafka/src/test/java/org/springframework/kafka/core/reactive
#EmbeddedKafka(topics = ReactiveKafkaProducerTemplateIntegrationTests.REACTIVE_INT_KEY_TOPIC, partitions = 2)
public class ReactiveKafkaProducerTemplateIntegrationTests {
...
added correct serialised with a non-transactional producer. please see the code on top of this page for the answer.
I have a Webflux application, where I have a ServerWebExchangeDecorator that decorates the request and responses. I have overrides to do some logging and then call the super methods.
This is what I have in code:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.ServerWebExchangeDecorator;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
import reactor.core.publisher.Mono;
#Component
public class LoggingWebFilter implements WebFilter {
#Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
return chain.filter(decorate(exchange));
}
private ServerWebExchange decorate(ServerWebExchange exchange) {
final ServerHttpRequest decoratedRequest = new LoggingServerHttpRequestDecorator(exchange.getRequest());
final ServerHttpResponse decoratedResponse = new LoggingServerHttpResponseDecorator(exchange.getResponse());
return new ServerWebExchangeDecorator(exchange) {
#Override
public ServerHttpRequest getRequest() {
return decoratedRequest;
}
#Override
public ServerHttpResponse getResponse() {
return decoratedResponse;
}
};
}
}
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
import reactor.core.publisher.Flux;
public class LoggingServerHttpRequestDecorator extends ServerHttpRequestDecorator {
private static final Logger logger = LoggerFactory.getLogger(LoggingServerHttpRequestDecorator.class);
public LoggingServerHttpRequestDecorator(ServerHttpRequest delegate) {
super(delegate);
}
#Override
public Flux<DataBuffer> getBody() {
logger.info("getBody method");
return super.getBody();
}
}
import org.reactivestreams.Publisher;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.http.server.reactive.ServerHttpResponseDecorator;
import reactor.core.publisher.Mono;
public class LoggingServerHttpResponseDecorator extends ServerHttpResponseDecorator {
private static final Logger logger = LoggerFactory.getLogger(LoggingServerHttpResponseDecorator.class);
public LoggingServerHttpResponseDecorator(ServerHttpResponse delegate) {
super(delegate);
}
#Override
public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
logger.info("writeWith method");//THIS LINE IS NOT EXECUTED WHEN AN EXCEPTION IS THROWN
return super.writeWith(body);
}
#Override
public Mono<Void> writeAndFlushWith(Publisher<? extends Publisher<? extends DataBuffer>> body) {
logger.info("writeAndFlushWith method");
return super.writeAndFlushWith(body);
}
}
When I do a happy path with a POST request, this works fine, but when an exception is thrown, the Response Decorator is omitted and my custom code is not being executed.
This is a controller code to replicate the issue:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
#RestController
#RequestMapping("/decorator-demo")
public class DecoratorDemoController {
/** The Constant logger. */
private static final Logger logger = LoggerFactory.getLogger(DecoratorDemoController.class);
#PostMapping(produces = MediaType.APPLICATION_STREAM_JSON_VALUE, consumes = MediaType.APPLICATION_STREAM_JSON_VALUE)
public Mono<ResponseEntity<String>> postData(#RequestBody String id) {
logger.info("attempting to post the data");
if(id.length() == 1){
Mono<String> created = Mono.just(id);
return created.flatMap(vo -> Mono.just(ResponseEntity.status(HttpStatus.CREATED).body(vo)));
}
throw new IllegalArgumentException("String length must be 1");
}
}
When I post a single character, I have the logs I am expecting:
LoggingServerHttpRequestDecorator : getBody method
DecoratorDemoController : attempting to post the data
LoggingServerHttpResponseDecorator : writeWith method
But when I post more than one character, this is the logs I am having:
LoggingServerHttpRequestDecorator : getBody method
DecoratorDemoController : attempting to post the data
AbstractErrorWebExceptionHandler : [0b933716] 500 Server Error for HTTP POST "/decorator-demo"
Am I doing something wrong, or missing something?
Try this
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import org.reactivestreams.Publisher;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.cloud.gateway.filter.NettyWriteResponseFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.http.server.reactive.ServerHttpResponseDecorator;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
#Component
public class ResponseInterceptorFilter implements Ordered, GlobalFilter {
private static final Logger log = LoggerFactory.getLogger(ResponseInterceptorFilter.class);
public ResponseInterceptorFilter(SqsPublisher sqsPublisher) {
this.sqsPublisher = sqsPublisher;
}
#Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpResponse response = exchange.getResponse();
DataBufferFactory dataBufferFactory = response.bufferFactory();
ServerHttpResponseDecorator decoratedResponse = new ServerHttpResponseDecorator(response) {
#Override
public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
if (!(body instanceof Flux)) {
return super.writeWith(body);
}
Flux<? extends DataBuffer> flux = (Flux<? extends DataBuffer>) body;
return super.writeWith(flux.buffer().map(dataBuffers -> {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
dataBuffers.forEach(buffer -> {
// three byte copies here
byte[] array = new byte[buffer.readableByteCount()];
buffer.read(array);
try {
outputStream.write(array);
} catch (IOException e) {
// TODO: need catch?
}
DataBufferUtils.release(buffer);
});
byte[] write = outputStream.toByteArray();
String responseBody = new String(write);
log.debug("Response ----> {}", responseBody);
response.getHeaders().setContentLength(write.length);
return dataBufferFactory.wrap(write);
}));
}
};
ServerWebExchange serverWebExchange = exchange.mutate().response(decoratedResponse).build();
return chain.filter(serverWebExchange);
}
#Override
public int getOrder() {
return NettyWriteResponseFilter.WRITE_RESPONSE_FILTER_ORDER - 1; // this is important
}
}
I have a simple MyLibraryApplication which is having code to invoke POST(TransactionControllerImpl.issueBookToMember) and PATCH(TransactionControllerImpl.returnBookTransaction) methods. I have referred some links on net and tried my best to write code to invoke PATCH method. The code can be found in TransactionControllerTest(testBookReturnUsingRestTemplate and testBookReturnUsingMockMvc methods). The code for invoking POST is working fine but the code for invoking PATCH is not working. Control never reaches returnBookTransaction inside TransactionControllerImpl.
Error: Invalid PATCH method.
I am looking for code snippet for TransactionControllerTest.testBookReturnUsingRestTemplate and testBookReturnUsingMockMvc methods. Can someone help me in getting this code into proper shape?
package com.mycompany.techtrial;
import java.util.Map;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import com.mycompany.techtrial.Transaction;
public interface TransactionController {
public ResponseEntity<Transaction> issueBookToMember(#RequestBody Map<String, String> params);
public ResponseEntity<Transaction> returnBookTransaction(#PathVariable(name="transaction-id") Long transactionId);
}
/**
*
*/
package com.mycompany.techtrial;
import java.time.LocalDateTime;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Optional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
#RestController
public class TransactionControllerImpl implements TransactionController{
/*
* PLEASE DO NOT CHANGE SIGNATURE OR METHOD TYPE OF END POINTS
* Example Post Request : { "book":"Java8 Primer","member":"Test 1" }
*/
#PostMapping(path = "/api/transaction")
public ResponseEntity<Transaction> issueBookToMember(#RequestBody Map<String, String> params){
String book = params.get("book");
String member = params.get("member");
Transaction transaction = new Transaction();
transaction.setId(1L);
transaction.setBook(book);
transaction.setMember(member);
transaction.setDateOfIssue(LocalDateTime.now());
transaction.setDateOfReturn(Transaction.getDefaultReturnDate());
return ResponseEntity.ok().body(transaction);
}
/*
* PLEASE DO NOT CHANGE SIGNATURE OR METHOD TYPE OF END POINTS
*/
#PatchMapping(path= "/api/transaction/{transaction-id}/return")
public ResponseEntity<Transaction> returnBookTransaction(#PathVariable(name="transaction-id") Long transactionId){
String book = "Java8 Primer";
String member = "Test 1";
Transaction transaction = new Transaction();
transaction.setId(1L);
transaction.setBook(book);
transaction.setMember(member);
transaction.setDateOfIssue(LocalDateTime.now().minusDays(10));
transaction.setDateOfReturn(LocalDateTime.now());
return ResponseEntity.ok().body(transaction);
}
}
package com.mycompany.techtrial;
import java.util.HashMap;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.ResultActions;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
#RunWith(SpringJUnit4ClassRunner.class)
#SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
public class TransactionControllerTest {
MockMvc mockMvc;
#Mock
private TransactionController transactionController;
#Autowired
private TestRestTemplate template;
#Before
public void setup() throws Exception {
mockMvc = MockMvcBuilders.standaloneSetup(transactionController).build();
}
#Test
public void testBookIssue() throws Exception {
HttpEntity<Object> transaction = getHttpEntity(
"{\"book\": \"Java8 Primer\", \"member\": \"Test 1\" }");
ResponseEntity<Transaction> response = template.postForEntity(
"/api/transaction", transaction, Transaction.class);
Assert.assertEquals("Java8 Primer", response.getBody().getBook());
Assert.assertEquals("Test 1", response.getBody().getMember());
Assert.assertEquals(200,response.getStatusCode().value());
}
#Test
public void testBookReturnUsingRestTemplate() throws Exception {
Long transactionId = new Long(1);
HashMap<String,Long> uriVariables = new HashMap<String,Long>();
uriVariables.put("transaction-id", transactionId);
Transaction transaction = template.patchForObject(
"/api/transaction/{transaction-id}/return",null, Transaction.class, uriVariables);
Assert.assertEquals(new Long(1), transaction.getId());
//Assert.assertEquals(200,response.getStatusCode().value());
}
#Test
public void testBookReturnUsingMockMvc() throws Exception {
Long transactionId = new Long(1);
HashMap<String,Long> uriVariables = new HashMap<String,Long>();
uriVariables.put("transaction-id", transactionId);
ResultActions obj = mockMvc.perform( MockMvcRequestBuilders
.patch("/api/transaction/{transaction-id}/return",transactionId)
.content("")
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON));
System.out.println(obj.getClass());
HttpStatus status = obj.andReturn().getModelAndView().getStatus();
boolean success = status.is2xxSuccessful();
System.out.println("success="+success);
Assert.assertEquals(new Long(1), transactionId);
//Assert.assertEquals(200,response.getStatusCode().value());
}
private HttpEntity<Object> getHttpEntity(Object body) {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
return new HttpEntity<Object>(body, headers);
}
}
package com.mycompany.techtrial;
import java.io.Serializable;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
public class Transaction implements Serializable {
private static final long serialVersionUID = 8951221480021840448L;
private static final LocalDateTime defaultReturnDate = LocalDateTime.of(LocalDate.of(2299, 12, 31), LocalTime.of(12, 0, 0));
Long id;
private String book;
private String member;
public String getBook() {
return book;
}
public void setBook(String book) {
this.book = book;
}
public String getMember() {
return member;
}
public void setMember(String member) {
this.member = member;
}
//Date and time of issuance of this book
LocalDateTime dateOfIssue;
//Date and time of return of this book
LocalDateTime dateOfReturn;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public LocalDateTime getDateOfIssue() {
return dateOfIssue;
}
public void setDateOfIssue(LocalDateTime dateOfIssue) {
this.dateOfIssue = dateOfIssue;
}
public LocalDateTime getDateOfReturn() {
return dateOfReturn;
}
public void setDateOfReturn(LocalDateTime dateOfReturn) {
this.dateOfReturn = dateOfReturn;
}
#Override
public String toString() {
return "Transaction [id=" + id + ", book=" + book + ", member=" + member + ", dateOfIssue=" + dateOfIssue + ", dateOfReturn=" + dateOfReturn + "]";
}
//#PrePersist
void preInsert() {
if (this.dateOfReturn == null)
this.dateOfReturn = defaultReturnDate;
}
public static LocalDateTime getDefaultReturnDate() {
return defaultReturnDate;
}
}
package com.mycompany.techtrial;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
#SpringBootApplication
public class MyLibraryApplication {
public static void main(String[] args) {
SpringApplication.run(MyLibraryApplication.class, args);
}
}
It seems to be a known issue with the RestTemplate default Http client.
RestTemplate bug
A workaround for this would be to use the apache httpcomponents httpclient library in the RestTemplateBuilder.setRequestFactory and pass that in the constructor to TestRestTemplate
After that you can use the exchange method on the TestRestTemplate class and do a PATCH request.
Sample code to create TestRestTemplate:
Supplier<ClientHttpRequestFactory> supplier = () -> {
return new HttpComponentsClientHttpRequestFactory();
};
restTemplateBuilder.requestFactory(supplier);
TestRestTemplate testRestTemplate = new TestRestTemplate(restTemplateBuilder);
testRestTemplate.exchange("/api/transaction/{transaction-id}/return",HttpMethod.PATCH,null,Transaction.class,uriVariables);
I'm new to Mockito and trying to test my service layer. My DAO layer is #Autowired in service and Hibernate is also autowired. Hibernate is not loading while testing. I always get NullPointerException. Here is my code:
EmployeeService (Interface)
package com.spring.crud.service;
import java.util.List;
import com.spring.crud.entity.Employee;
public interface EmployeeService {
Employee save(Employee employee);
boolean update(Employee employee);
Employee find(Integer id);
List<Employee> getEmployees();
boolean remove(Integer id);
}
EmployeeServiceImpl (Class)
package com.spring.crud.service;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.spring.crud.dao.EmployeeDAO;
import com.spring.crud.entity.Employee;
#Service
public class EmployeeServiceImpl implements EmployeeService {
#Autowired
private EmployeeDAO dao;
public Employee save(Employee employee) {
return dao.save(employee);
}
public boolean update(Employee employee) {
return dao.update(employee);
}
public Employee find(Integer id) {
return dao.find(id);
}
public List<Employee> getEmployees() {
return dao.getEmployees();
}
public boolean remove(Integer id) {
return dao.remove(id);
}
}
EmployeeDAO (Interface)
package com.spring.crud.dao;
import java.util.List;
import com.spring.crud.entity.Employee;
public interface EmployeeDAO {
Employee save(Employee employee);
boolean update(Employee employee);
Employee find(Integer id);
List<Employee> getEmployees();
boolean remove(Integer id);
}
EmployeeDAOImpl (Class)
package com.spring.crud.dao;
import java.util.List;
import javax.transaction.Transactional;
import org.hibernate.Query;
import org.hibernate.Session;
import org.springframework.stereotype.Repository;
import com.spring.crud.entity.Employee;
#Transactional
#Repository
public class EmployeeDAOImpl extends HibernateUtil implements EmployeeDAO{
public Employee save(Employee employee) {
Session session = getCurrentSession();
session.save(employee);
return employee;
}
public boolean update(Employee employee) {
Session session = getCurrentSession();
session.update(employee);
return false;
}
public Employee find(Integer id) {
Session session = getCurrentSession();
Employee employee = (Employee)session.get(Employee.class, id);
return employee;
}
public List<Employee> getEmployees() {
Session session = getCurrentSession();
Query query = session.createQuery("from Employee");
#SuppressWarnings("unchecked")
List<Employee> employees = (List<Employee>)query.list();
return employees;
}
public boolean remove(Integer id) {
Session session = getCurrentSession();
Employee employee = (Employee)session.get(Employee.class, id);
if(employee!=null){
session.delete(employee);
return true;
}
return false;
}
}
HibernateUtil
package com.spring.crud.dao;
import javax.annotation.PostConstruct;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;
public class HibernateUtil extends HibernateDaoSupport{
#Autowired
private SessionFactory sessionFactory;
#PostConstruct
public void init() {
setSessionFactory(sessionFactory);
}
public Session getCurrentSession() {
return sessionFactory.getCurrentSession();
}
}
EmployeeServiceTest (Test class)
package com.spring.crud.test;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import org.junit.Before;
import org.junit.Test;
import org.mockito.MockitoAnnotations;
import org.mockito.Spy;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.springframework.test.context.ContextConfiguration;
import com.spring.crud.config.WebConfig;
import com.spring.crud.dao.EmployeeDAO;
import com.spring.crud.dao.EmployeeDAOImpl;
import com.spring.crud.entity.Employee;
import com.spring.crud.service.EmployeeService;
import com.spring.crud.service.EmployeeServiceImpl;
#ContextConfiguration(classes = {WebConfig.class})
public class EmployeeServiceTest {
private EmployeeDAO employeeDAO;
private EmployeeService employeeService = new EmployeeServiceImpl();
#Spy
List<Employee> employees = new ArrayList<Employee>();
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
employeeDAO = mock(EmployeeDAOImpl.class);
}
#Test
public void listEmployees() {
}
#Test
public void create() {
Employee employee = new Employee();
employee.setDateOfBirth(new Date());
employee.setGender("male");
employee.setName("Ashutosh");
when(employeeDAO.save(any(Employee.class)))
.thenAnswer(new Answer<Employee>() {
public Employee answer(InvocationOnMock invocation) throws Throwable {
Employee employee = (Employee) invocation.getArguments()[0];
employee.setId(1);
return employee;
}
});
assertNull(employee.getId());
employee = employeeService.save(employee);
assertNotNull(employee.getId());
assertTrue(employee.getId()>0);
}
#Test
public void edit() {
}
#Test
public void update() {
}
#Test
public void remove() {
}
}
I can't find much on this on the internet.
Just because you create a mock employee DAO in your test doesn't mean that your service will use it. It won't. When you do
new EmployeeServiceImpl();
you create an instance of the service, and its DAO field is left uninitialized (so null).
Use constructor injection, and pass the mock DAO to the service constructor:
public class EmployeeServiceImpl implements EmployeeService {
private EmployeeDAO dao;
#Autowired
public EmployeeServiceImpl(EmployeeDAO dao) {
this.dao = dao;
}
...
}
And/or at least use Mockito annotations correctly:
#Mock
private EmployeeDAO employeeDAO;
#InjectMocks
private EmployeeServiceImpl employeeService;
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
}
OK, I found some fixes and now the test runs.
First, I fixed the HibernateUtil
package com.spring.crud.dao;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
public class HibernateUtil{
#Autowired
private SessionFactory sessionFactory;
public Session getCurrentSession() {
return sessionFactory.getCurrentSession();
}
}
This is the EmployeeServiceTest class
package com.spring.crud.test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.when;
import java.util.Date;
import org.junit.Before;
import org.junit.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.springframework.test.context.ContextConfiguration;
import com.spring.crud.config.WebConfig;
import com.spring.crud.dao.EmployeeDAO;
import com.spring.crud.entity.Employee;
import com.spring.crud.service.EmployeeServiceImpl;
#ContextConfiguration(classes = {WebConfig.class})
public class EmployeeServiceTest {
#Mock
private EmployeeDAO employeeDAO;
#InjectMocks
private EmployeeServiceImpl employeeService;
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
}
#Test
public void listEmployees() {
}
#Test
public void create() {
Employee employee = new Employee();
employee.setDateOfBirth(new Date());
employee.setGender("male");
employee.setName("Ashutosh");
when(employeeDAO.save(any(Employee.class)))
.thenAnswer(new Answer<Employee>() {
public Employee answer(InvocationOnMock invocation) throws Throwable {
Employee employee = (Employee) invocation.getArguments()[0];
employee.setId(10);
return employee;
}
});
assertNull(employee.getId());
employee = employeeService.save(employee);
System.out.println("Id = " + employee.getId());
assertNotNull(employee);
assertEquals((Integer)10, (Integer)employee.getId());
}
#Test
public void edit() {
}
#Test
public void update() {
}
#Test
public void remove() {
}
}