How to workaround orElseThrow mock - java

I have a ProductService that through ProductRepository queries a database. In there I have an update method and a find method. The update method is updateProductInDatabase(String id, Product updateInfo). The updateProductInDatabase calls a method findProductInDatabaseById(String id) which return Product or throws a ResourceNotFoundException.
My code:
public void updateProductInDatabase(String id, Product updateInfo) {
Product product = findProductInDatabaseById(id);
if (correctFormat(updateInfo.getVersion()) {
product.setVersion(updateInfo.getVersion());
repository.save(updateInfo);
//restOfTheCode
} else {
// Throws invalid input exception
}
}
private Products findProductInDatabaseById(String id) {
Optional<Product> productOptional =
repository.getAllProducts().stream.findFirst(); // return a list, but I only need the first
return productOptional.orElseThrow(...) // Throws resource not found exception
}
I want to write unit test for this code that expects the invalid input exception, but the test fails with
unexpected exception: expected InvalidInputException, but found
ResourceNotFoundException
This happens because productOptional is always an empty optional.
Can someone help in providing a workaround to mocking productOptional ?
Edit: adding my test
#Test(expected = InvalidInputException.class)
public void testUpdateProductVersionInDatabaseWhenVersionIsIncorrectFormat()
throws ApiException {
Product product = new Product();
product.setVersion("error-version");
when(repository.getAllProducts())
.thenReturn(Collections.singletonList(new Product()));
productService.updateProductInDatabase("product-id-1", product);
}

Can you do something like
List<Product> products = new LinkedList<>();
products.add(new Product()); // add product
Mockito.when(repository.getAllProducts()).thenReturn(products);

It means that the state of the DB isn't sutable for the test since you expect some data meanwhie there are no products. Insert some product before the test.
If you really don't care of the data, then yes, it could be mocked:
class ProductServiceTestClass {
#Mock
ProductRepository repository;
#InjectMocks
ProductService productService;
#Test(expected = InvalidInputException.class)
public void testUpdateProductVersionInDatabaseWhenVersionIsIncorrectFormat()
throws ApiException {
Product product = new Product();
product.setVersion("error-version");
when(repository.getAllProducts())
.thenReturn(Collections.singletonList(new Product()));
productService.updateProductInDatabase("product-id-1", product);
}
}
Note that it won't work if ProductRepository injected by #Autowired. In this case the real Spring bean will be used instead. Change its injection to constructor or setter way.

You should refactor your code so the validation method can be unit tested
Extract method correctFormat(updateInfo.getVersion() to a utility class like :
VersionValidator.isCorrectFormat(String version)
you will be able to unit test it :
#Test
public void should_return_false_on_incorrect_format_version()
throws ApiException {
String uncorrectFormat = "LTS.x.v15dssdf";
boolean isCorrect = VersionValidator.isCorrectFormat(uncorrectFormat);
assertThat(isCorrect).isFalse()
}
anyway, if you want to keep with an integration test, your method repository.getAllProducts() should at least return a product.
public void updateProductInDatabase(String id, Product updateInfo) {
if (!correctFormat(updateInfo.getVersion()) {
//throw
}
Product product = findProductInDatabaseById(id);
product.setVersion(updateInfo.getVersion());
repository.save(updateInfo);
//restOfTheCode
}

Related

Should my custom exception be checked or unchecked in my spring boot application

I'm writing a simple online shop using spring boot, for learning purposes. Right now I have purchasing-service and a product-service. The purchasing-service makes requests to the product-service via REST APIs for querying for product information. All of this is working, but I'm unclear on if I'm using exceptions correctly within the product-service.
I have an endpoint in my controller called retrieveProductById that retrieves information about a product given the product id. This id is passed to a service, which then uses a repository to look up the product in the database and return the product information. If the product id does not exist in the database, it throws a ProductDoesNotExistException. This is then handled by an ErrorHandlerControllerAdvice class that simply returns a 400 bad request response with the erorr message to the caller.
My question is: should my ProductDoesNotExistException be checked, or unchecked? To me, it makes sense for it to be checked, because as a developer or even a consumer of the API, you can clearly infer that if you give an invalid product id to the API, it will throw a ProductDoesNotExistException. If I change the exception to be unchecked, this is not as clear, as the IDE does not enforce me to explicitly add a throws ProductDoesNotExistException to the method signature.
So what would be the correct approach here? I do feel like it almost doesn't matter which I choose, as the controller advice class will handle the exception regardless of the type when it is thrown, so is the decision just a matter of preference on the developer part? See code below:
Controller
#RestController
#RequestMapping("/products")
#Slf4j
public class ProductController {
private final ProductService productService;
private final RestApiResponseFactory restApiResponseFactory;
public ProductController(final ProductService productService,
final RestApiResponseFactory restApiResponseFactory) {
this.productService = productService;
this.restApiResponseFactory = restApiResponseFactory;
}
#GetMapping("/{productId}")
public ResponseEntity<RestApiResponse<ProductVO>> retrieveProductById(
#PathVariable final String productId) throws ProductDoesNotExistException {
log.info("Request made to find product {}", productId);
final ProductVO productVO = this.productService.retrieveProductById(
Integer.parseInt(productId));
return ResponseEntity.ok(this.restApiResponseFactory.createSuccessResponse(productVO));
}
}
Service
#Service
#Slf4j
public class ProductService {
private final ProductRepository productRepository;
public ProductService(final ProductRepository productRepository) {
this.productRepository = productRepository;
}
public ProductVO retrieveProductById(final int productId) throws ProductDoesNotExistException {
final Product product = findProductById(productId).orElseThrow(
() -> new ProductDoesNotExistException(productId));
return new ProductVO(product);
}
private Optional<Product> findProductById(final int productId) {
return this.productRepository.findById(productId);
}
}
Exception
public class ProductDoesNotExistException extends Exception {
public ProductDoesNotExistException(final int productId) {
super("No product with Product ID " + productId + " exists");
}
}
Error handler
#ControllerAdvice
public class ErrorHandlerControllerAdvice {
private final RestApiResponseFactory restApiResponseFactory;
public ErrorHandlerControllerAdvice(final RestApiResponseFactory restApiResponseFactory) {
this.restApiResponseFactory = restApiResponseFactory;
}
/**
* Return 400 if a product does not exist
*/
#ExceptionHandler(ProductDoesNotExistException.class)
public ResponseEntity<RestApiResponse> handleProductDoesNotExistException(
final ProductDoesNotExistException e) {
return ResponseEntity.badRequest()
.body(restApiResponseFactory.createErrorResponse(e.getMessage()));
}
/**
* Catch all exception handler. In case any exceptions slip through the cracks, we want to
* return a 500
*
* #param e the exception
* #return
*/
#ExceptionHandler(Exception.class)
public ResponseEntity<RestApiResponse> handleGeneralException(final Exception e) {
return ResponseEntity.internalServerError()
.body(restApiResponseFactory.createErrorResponse(e.getMessage()));
}
}

Junit Code Coverage for Custom Row Mapper

I have written a Test class for getEmployeeDetails Method contains a procedure which will return employee details in a cursor, a custom row mapper implemented to map those fields.
I am able to mock the StoredProcedure call in the method and execute the method, But when i look at the code coverage Custom Row Mapper is not included as part of the Code Coverage
Below is My Repository Class
public class EmployeeRepository {
#Autowired
private JdbcRepository jdbcRepository
public List<Employee> getEmployeeDetails() {
SqlParameter[] sqlParameters = new SqlParameter[] {
new SqlOutParameter("employeedetails", OracleTypes.CURSOR, new EmployeeListHandler()) };
Map<String, Object> result = jdbcRepository.executeProcedure("employee_list_proc", sqlParameters, new HashMap<>());
return (List<Employee>) result.get("employeedetails");
}
private class EmployeeListHandler implements RowMapper<Employee> {
#Override
public Employee mapRow(ResultSet rs, int rowNum) throws SQLException {
Employee employee = new Employee();
employee.setId(rs.getLong("id");
employee.setName(rs.getString("name"));
// Rest of the employee Attributes
return employee;
}
}
}
Below Is My Custom JbdcRepository Class
public Class JdbcRepository {
public Map<String, Object> executeProcedure(String procedureName, SqlParameter[] sqlParameters,
Map<String, Object> inputParams){
StoredProcedure procedure = new GenericStoredProcedure();
setDataSource(procedure);
procedure.setSql(procedureName);
procedure.setFunction(false);
procedure.setParameters(sqlParameters);
return procedure.execute(inputParams);
}
}
Below Is my Test Class
#Mock
private JdbcRepository jdbcRepository;
#Mock
private ResultSet rs;
#InjectMocks
private EmployeeRepository employeeRepository;
#Test
public void testEmployeeDetails() throws Exception{
when(rs.next()).thenReturn(true).thenReturn(false);
when(rs.getString(anyString())).thenReturn("TEST");
when(jdbcRepository.executeProcedure(anyString(), any(SqlParameter[].class),
anyMap())).thenReturn(new HashMap<>());
employeeRepository.getEmployeeDetails();
verify(jdbcRepository, times(1)).executeProcedure(anyString(), any(SqlParameter[].class),
anyMap());
verifyNoMoreInteractions(jdbcRepository);
}
I have mocked the resultset, But still Code coverage is not included for the EmployeeListHandler, Not sure where i am doing wrong ..
Edit 1: If i am doing wrong, what is alternative way to get the complete code coverage .
Notice that here:
public List<Employee> getEmployeeDetails() {
SqlParameter[] sqlParameters = new SqlParameter[] {
new SqlOutParameter("employeedetails", OracleTypes.CURSOR, new EmployeeListHandler()) };
Map<String, Object> result = jdbcRepository.executeProcedure("employee_list_proc", sqlParameters, new HashMap<>());
return (List<Employee>) result.get("employeedetails");
}
You created sqlParameters and added EmployeeListHandler object as one if it's parameters.
Then You used sqlParameters with executeProcedure so your overridden function mapRow is supposed to be called when executeProcedure is called, but you also
mocked it's call
when(jdbcRepository.executeProcedure(anyString(), any(SqlParameter[].class),
anyMap())).thenReturn(new HashMap<>());
so there's no coverage on this function as it's never being called when you test it.
Answer for edit:
You should test MapRow separately.
And also, I'm not sure what you're trying to test here since almost all your function calls are mocked so this test doesn't really test anything.
If you want to actually test a real call to the repository you'll need to setup one and test it.

Test method fails when #Transactional

I am writing my end-to-end tests for an application. One of those tests will verify that when some required input is missing, it will not persist the data to the database. All is running fine, untill I decided to annotate my test class with #Transactional, since I don't need to persist the results when the tests are finished. The moment I add the #Transactionalannotation, the application is suddenly ok with a non-null field to be null.
How the database is set up:
create table MY_TABLE
(
MY_FIELD VARCHAR(20) NOT NULL
)
The corresponding model:
#Entity
#Table(name = "MY_TABLE")
public class MyObject {
#Column(name = "MY_FIELD", nullable = false)
private String MyField = null;
}
Saving goes via the service layer:
#Service
public class MyAppServiceImpl implements MyAppService {
#Inject
private MyObjectRepository myObjectRepository; //this interface extends CrudRepository<MyObject , Long>
#Override
#Transactional
public MyObject save(MyObject myObject) {
return myObjectRepository.save(myObject);
}
}
In the resource layer, the value of myField is set to NULL, so at the moment the data is persisted, I should receive an ORA-error
#Controller
public class MyAppResourceImpl implements MyAppResource {
#Inject
private MyAppService myAppService;
public ResponseEntity doPost(#RequestBody String xml) {
MyObject myobject = new MyObject();
myObject.setMyField(null);
try {
myAppService.save(myObject);
return new ResponseEntity(null, HttpStatus.CREATED);
} catch (SomeExceptions e) {
return new ResponseEntity("Could not save", HttpStatus.BAD_REQUEST);
}
}
}
Finally, the test:
#RunWith(SpringRunner.class)
#SpringBootTest
//#Transactional
public class MyAppResourceImplEndToEndTest {
#Inject
private MyAppResourceImpl myAppResource;
#Test
public void testWithFieldNull() throws Exception {
ResponseEntity response = myAppResource.doPost("some-xml");
Assertions.assertThat(response.GetStatusCode()).isEqualTo(HttpStatus.BAD_REQUEST);
}
}
This test works, as expected, but other tests persist data into the database. This is not the behavior I wanted, so I annotated the test class with #Transactional. Now no tests persist their results to the database, but this specific test method fails because
Expected :400
Actual :201
Why does the #Transactional annotation suddenly cause NULL values to be persisted in NOT NULL columns?

Am I testing this correctly?

I have the following method on a service class:
#Service
public class Service {
(...)
public Page<ChannelAccount> getByCustomerAndChannelType(Pageable pageable, Customer customer, ChannelType channelType) {
return channelAccountRepository.findByCustomerAndChannelType(pageable, customer, channelType);
}
}
This returns the expected result. Now I trying to build the unit test for it. So far I got this:
#RunWith(MockitoJUnitRunner.class)
public class ChannelAccountServiceTest {
#InjectMocks
private ChannelAccountService channelAccountService;
#Mock
private ChannelAccountRepository channelAccountRepository;
(...)
#Test
public void testGetByCustomerAndChannelTypePageable() {
Page<ChannelAccount> pageResult = new PageImpl<>(channelAccountService.getAllChannelAccounts());
Mockito.when(channelAccountRepository.findByCustomerAndChannelType(pageable, customer, ChannelType.FACEBOOK)).thenReturn(pageResult);
Page<ChannelAccount> channelAccountPage = channelAccountRepository.findByCustomerAndChannelType(pageable, customer, ChannelType.FACEBOOK);
assertEquals(pageResult, channelAccountPage);
}
Somehow this doesn't feels right. What am I missing here?
Not sure why you are calling this method as it has nothing to do with the case itself:
Page<ChannelAccount> pageResult = new PageImpl<>(channelAccountService.getAllChannelAccounts());
I would do the following in the test:
Pageable pageableStub = Mockito.mock(Pageable.class);
Page pageStub = Mockito.mock(Page.class);
Mockito.when(channelAccountRepository
.findByCustomerAndChannelType(pageableStub, customer, ChannelType.FACEBOOK))
.thenReturn(pageStub);
Page<ChannelAccount> channelAccountPage = channelAccountService
.findByCustomerAndChannelType(pageableStub, customer, ChannelType.FACEBOOK);
assertTrue(pageResult == channelAccountPage);
I would check whether the objects are the same instances instead of equals (even more strict).

Junit/Mockito unit with Spring bean dependencies

I need to test following method inside ProductManager class which is a Spring bean. productService a bean and injected into ProductManager class. I try to write a Junit test using Mockito, but it kept to invoke real productService.getProductIdList(email) method instead of mock. ProductService also has a #PostConstruct. Could anyone enlighten me what's wrong with my test code?
#Named(ProductManager.NAME)
public class ProductManager {
#Resource(name = ProductService.NAME)
private ProductService productService;
public static final String NAME = "productManager";
public Set<Product> getProducts(String email) {
Set<Integer> productList = productService.getProductIdList(email);
Iterator<Integer> itr = productList.iterator();
Set<Product> products = new HashSet<Product>();
Product p = null;
while (itr.hasNext()) {
p = getProduct(itr.next());
if (p != null) {
products.add(p);
}
}
return products;
}
public Product getProduct(Integer ProductId) {
Product p = productService.getProduct(productId);
return p;
}
}
So far, I have following Junit test code.
#Test
public void getProductByEmail(){
String email = "testfakeuser#gmail.com";
ProductService mockProductService = mock(ProductServiceImpl.class);
Integer productId1 = 10000;
Integer productId2 = 10002;
Product p1 = mock(Product.class);
Product p2 = mock(Product.class);
when(p1.getProductId()).thenReturn(productId1);
when(p2.getProductId()).thenReturn(productId2);
Set<Integer> productIdSet = (Set<Integer>)mock(Set.class);
productIdSet.add(productId1);
productIdSet.add(productId2);
Iterator<Integer> productIdIterator = (Iterator<Integer>)mock(Iterator.class);
when(productIdSet.iterator()).thenReturn(productIdIterator);
when(mockProductService.getProductIdList(email)).thenReturn(productIdSet);
when(productIdIterator.hasNext()).thenReturn(true, true, false);
when(productIdIterator.next()).thenReturn(productId1).thenReturn(productId2);
when(productManager.getProduct(productId1)).thenReturn(p1);
when(productManager.getProduct(productId2)).thenReturn(p2);
Set<Product> products = productManager.getProducts(email);
assertEquals(2, products.size());
}
I don't see any where where you set your mocked ProductService in to the ProductManager object.
You've created a intricate set of related objects, but not asked the ProductManager to use it.
I am answering to my own question. I resolved the issue using Spring ReflectionTestUtils. It can set mock dependencies. I referenced http://romiawasthy.blogspot.com/2012/03/autowired-mockobjects-for-junits.html. I tried second solution, but I couldn't make it work.
In this Test, the productManager is not mocked. It is a real spring bean. Another way of doing is don't use Spring context at all, just use Mockito RunWith(MockitoJUnitRunner.class). In this case all beans are mocked including the productManager. I did some research yesterday and using MockitoJUnitRunner.class is preferable since it can void reapeating code and you have full control of your test environment. Please look at this article for using MockitoJUnitRunner.class http://www.jayway.com/2012/02/25/mockito-and-dependency-injection/. It is clear and simple.
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration("classpath:context-test.xml")
public class ProductManagerTest {
#Inject
private ProductManager productManager;
#Test
public void getProductByEmail(){
String email = "testfakeuser#gmail.com";
ProductService mockProductService = mock(ProductServiceImpl.class);
Integer productId1 = 10000;
Integer productId2 = 10002;
Product p1 = mock(Product.class);
Product p2 = mock(Product.class);
p1.setProductId(productId1);
p2.setProductId(productId2);
Set<Integer> productIdSet = (Set<Integer>)mock(Set.class);
productIdSet.add(productId1);
productIdSet.add(productId2);
Iterator<Integer> productIdIterator = (Iterator<Integer>)mock(Iterator.class);
when(productIdSet.iterator()).thenReturn(productIdIterator);
when(mockProductService.getProductIdList(email)).thenReturn(productIdSet);
ReflectionTestUtils.setField(productManager, "mockProductService",
mockProductService);
when(productIdIterator.hasNext()).thenReturn(true, true, false);
when(productIdIterator.next()).thenReturn(productId1).thenReturn(productId2);
when(productManager.getProduct(productId1)).thenReturn(p1);
when(productManager.getProduct(productId2)).thenReturn(p2);
Set<Product> products = productManager.getProducts(email);
assertEquals(2, products.size());
}
}

Categories

Resources