Commit 1e10cd9f authored by Ashok Kumar K's avatar Ashok Kumar K

added unit tests to service layer and few code changes

parent 6cbe12e3
package com.nisum.reactiveweb; package com.nisum.reactiveweb;
import lombok.extern.slf4j.Slf4j;
import reactor.blockhound.BlockHound; import reactor.blockhound.BlockHound;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.util.concurrent.atomic.AtomicInteger;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.event.ApplicationStartedEvent;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
@SpringBootApplication @SpringBootApplication
public class ReactiveWebApplication { public class ReactiveWebApplication {
......
...@@ -10,6 +10,7 @@ import org.springframework.beans.factory.annotation.Autowired; ...@@ -10,6 +10,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.support.DefaultMessageSourceResolvable; import org.springframework.context.support.DefaultMessageSourceResolvable;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import org.springframework.validation.BeanPropertyBindingResult; import org.springframework.validation.BeanPropertyBindingResult;
import org.springframework.validation.Errors; import org.springframework.validation.Errors;
import org.springframework.validation.Validator; import org.springframework.validation.Validator;
...@@ -77,6 +78,7 @@ public class ProductHandler { ...@@ -77,6 +78,7 @@ public class ProductHandler {
} }
private void validateProduct(Product product) { private void validateProduct(Product product) {
Assert.notNull(product, "product can't be null");
Errors errors = new BeanPropertyBindingResult(product, "product"); Errors errors = new BeanPropertyBindingResult(product, "product");
validator.validate(product, errors); validator.validate(product, errors);
String errorString = errors.getAllErrors(). String errorString = errors.getAllErrors().
......
package com.nisum.reactiveweb.exceptions; package com.nisum.reactiveweb.exceptions;
import lombok.extern.slf4j.Slf4j;
import java.util.Map; import java.util.Map;
import org.springframework.boot.web.error.ErrorAttributeOptions; import org.springframework.boot.web.error.ErrorAttributeOptions;
...@@ -8,19 +10,28 @@ import org.springframework.http.HttpStatus; ...@@ -8,19 +10,28 @@ import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.server.ServerRequest; import org.springframework.web.reactive.function.server.ServerRequest;
import static java.util.Optional.ofNullable;
import static org.springframework.http.HttpStatus.BAD_REQUEST;
import static org.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR;
import static org.springframework.http.HttpStatus.NOT_FOUND;
@Component @Component
@Slf4j
public class ApiError extends DefaultErrorAttributes { public class ApiError extends DefaultErrorAttributes {
@Override @Override
public Map<String, Object> getErrorAttributes(ServerRequest request, ErrorAttributeOptions options) { public Map<String, Object> getErrorAttributes(ServerRequest request, ErrorAttributeOptions options) {
Map<String, Object> errorAttributes = super.getErrorAttributes(request, options); Map<String, Object> errorAttributes = super.getErrorAttributes(request, options);
Throwable error = getError(request); Throwable error = getError(request);
log.error("{} : {}", error.getClass().getCanonicalName(), error.getMessage());
if (error instanceof ProductNotFoundException) { if (error instanceof ProductNotFoundException) {
addErrorFields(errorAttributes, error, HttpStatus.NOT_FOUND); addErrorFields(errorAttributes, error, NOT_FOUND);
} else if (error instanceof IllegalArgumentException) { } else if (error instanceof IllegalArgumentException) {
addErrorFields(errorAttributes, error, HttpStatus.BAD_REQUEST); addErrorFields(errorAttributes, error, BAD_REQUEST);
} else { } else {
addErrorFields(errorAttributes, error, HttpStatus.INTERNAL_SERVER_ERROR); int statusCode = (int) ofNullable(errorAttributes.get("status")).orElse(500);
HttpStatus status = ofNullable(HttpStatus.resolve(statusCode)).orElse(INTERNAL_SERVER_ERROR);
addErrorFields(errorAttributes, error, status);
} }
return errorAttributes; return errorAttributes;
} }
......
...@@ -40,7 +40,7 @@ public class GlobalExceptionHandler extends AbstractErrorWebExceptionHandler { ...@@ -40,7 +40,7 @@ public class GlobalExceptionHandler extends AbstractErrorWebExceptionHandler {
private Mono<ServerResponse> handlerFunction(ServerRequest request) { private Mono<ServerResponse> handlerFunction(ServerRequest request) {
Map<String, Object> errorAttributes = getErrorAttributes(request, ErrorAttributeOptions.defaults()); Map<String, Object> errorAttributes = getErrorAttributes(request, ErrorAttributeOptions.defaults());
int status = (int) Optional.of(errorAttributes.get("status")).orElse(500); int status = (int) Optional.ofNullable(errorAttributes.get("status")).orElse(500);
return ServerResponse.status(status) return ServerResponse.status(status)
.contentType(MediaType.APPLICATION_JSON) .contentType(MediaType.APPLICATION_JSON)
......
...@@ -31,7 +31,12 @@ public class ProductRepositoryImpl implements ProductRepository { ...@@ -31,7 +31,12 @@ public class ProductRepositoryImpl implements ProductRepository {
@Override @Override
public Mono<Product> saveProduct(Product product) { public Mono<Product> saveProduct(Product product) {
return template.save(product); return Mono.justOrEmpty(product)
.map(prod -> {
prod.setSku(null);
return prod;
})
.flatMap(template::save);
} }
@Override @Override
......
...@@ -29,14 +29,14 @@ public class ProductServiceImpl implements ProductService { ...@@ -29,14 +29,14 @@ public class ProductServiceImpl implements ProductService {
@Override @Override
public Mono<Product> saveProduct(Product productToSave) { public Mono<Product> saveProduct(Product productToSave) {
return Mono.just(productToSave) return Mono.justOrEmpty(productToSave)
.doOnNext(product -> product.setSku(null))
.flatMap(repository::saveProduct); .flatMap(repository::saveProduct);
} }
@Override @Override
public Mono<Product> updateProduct(String sku, Product product) { public Mono<Product> updateProduct(String sku, Product product) {
return getProductBySku(sku) return repository.getProductBySku(sku)
.switchIfEmpty(Mono.error(new ProductNotFoundException()))
.then(repository.updateProduct(sku, product)); .then(repository.updateProduct(sku, product));
} }
......
package com.nisum.reactiveweb.repository;
import org.junit.jupiter.api.Test;
class ProductRepositoryTest {
@Test
void getProductBySku() {
}
@Test
void getAllProducts() {
}
@Test
void saveProduct() {
}
@Test
void updateProduct() {
}
@Test
void removeProduct() {
}
}
\ No newline at end of file
package com.nisum.reactiveweb.service;
import com.nisum.reactiveweb.exceptions.ProductNotFoundException;
import com.nisum.reactiveweb.model.Product;
import com.nisum.reactiveweb.repository.ProductRepository;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import java.math.BigDecimal;
import java.util.function.Predicate;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.springframework.boot.test.context.SpringBootTest;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.when;
import static reactor.core.publisher.Mono.just;
@SpringBootTest
class ProductServiceTest {
@InjectMocks
private ProductServiceImpl productService;
@Mock
private ProductRepository repository;
private final Product product1 = new Product("1", "toy", new BigDecimal(200));
private final Product product3 = new Product("1", "toy", new BigDecimal(300));
private final Product product2 = new Product("2", "table", new BigDecimal(400));
private final Predicate<Product> productPredicate1 = productPredicate("1", "toy", 200);
private final Predicate<Product> productPredicate3 = productPredicate("1", "toy", 300);
private final Predicate<Product> productPredicate2 = productPredicate("2", "table", 400);
@Test
void getProductBySkuWhenProductExists() {
when(repository.getProductBySku("1")).thenReturn(productMono());
StepVerifier.create(productService.getProductBySku("1"))
.expectNextMatches(productPredicate1)
.verifyComplete();
}
@Test
void getProductBySkuWhenProductDoesNotExist() {
when(repository.getProductBySku("1")).thenReturn(Mono.empty());
StepVerifier.create(productService.getProductBySku("1"))
.expectError(ProductNotFoundException.class)
.verify();
}
@Test
void getAllProducts() {
when(repository.getAllProducts()).thenReturn(productsFlux());
StepVerifier.create(productService.getAllProducts())
.expectNextMatches(productPredicate1)
.as("predicate1")
.expectNextMatches(productPredicate2)
.as("predicate2")
.verifyComplete();
}
@Test
void saveProduct() {
when(repository.saveProduct(any(Product.class))).thenReturn(productMono());
StepVerifier.create(productService.saveProduct(product1))
.expectNextMatches(productPredicate1)
.verifyComplete();
}
@Test
void updateProductWhenProductExists() {
when(repository.getProductBySku(anyString())).thenReturn(productMono());
when(repository.updateProduct(anyString(), any(Product.class))).thenReturn(just(product3));
StepVerifier.create(productService.updateProduct("1", product3))
.expectNextMatches(productPredicate3)
.verifyComplete();
}
@Test
void updateProductWhenProductDoesNotExist() {
when(repository.getProductBySku("1")).thenReturn(Mono.empty());
when(repository.updateProduct(anyString(), any(Product.class))).thenReturn(just(product3));
StepVerifier.create(productService.updateProduct("1", product3))
.expectError(ProductNotFoundException.class)
.verify();
}
@Test
void removeProductWhenProductExists() {
when(repository.removeProduct(anyString())).thenReturn(just(1L));
StepVerifier.create(productService.removeProduct("1"))
.expectNext("deleted")
.verifyComplete();
}
@Test
void removeProductWhenProductDoesNotExist() {
when(repository.removeProduct(anyString())).thenReturn(just(0L));
StepVerifier.create(productService.removeProduct("1"))
.expectError(ProductNotFoundException.class)
.verify();
}
private Mono<Product> productMono() {
return just(product1);
}
private Flux<Product> productsFlux() {
return Flux.just(product1, product2);
}
private Predicate<Product> productPredicate(String sku, String name, int price) {
return product -> product.getSku().equals(sku)
&& product.getName().equals(name)
&& product.getPrice().intValue() == price;
}
}
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment