Camel retry control with multiple exceptions - exception

Preface: I'm fairly new to Camel, and after digesting Camel in action as best as possible, I'm adapting it to a project I'm on. In this project, we have some rather complex error handling, and I want to make sure I can replicate this as we Camel-ize our code.
On our project (as most) there are a set of Exceptions we want to retry and a set that we don't - but more specifically, there are a set that we want to retry more than others (not all recoverable errors can be treated the same). In this case, I was attempting to define an onException block to change the redelivery policy. However, it seems that the Exchange maintains the count (Exchange.REDELIVERY_COUNTER) and that this count is not dependent on which exception is thrown. Is there a way to make this count be specific for a given exception?
For example - I have two exceptions FooException and BarException. In my route (or really in the whole context), I want to retry FooExceptions 10 times, but BarExceptions should only retry 2 times. So the context will contain:
<onException>
<exception>my.exception.FooException</exception>
<redeliveryPolicy maximumRedeliveries="10" redeliveryDelay="2000"
</onException>
<onException>
<exception>my.exception.BarException</exception>
<redeliveryPolicy maximumRedeliveries="2" redeliveryDelay="5000"
</onException>
Now, the concern - if my application throws a FooException and retries 4 times (each time throwing a FooException) and then on the 5th attempt, it throws a BarException, it seems that the way this works is the Exchange will have a REDELIVERY_COUNTER of 5, and when I reset the policy to only try twice, it (logically) concludes that the route should not be retried and throws the exception back out. However, in my application BarExceptions should be retried twice, regardless of how many FooExceptions get thrown. And likewise, if it alternates throwing Foo and Bar exceptions, I would like it to only increment the counter for the given exception.
The very end of Camel in Action promotes using a retryWhile - is this the only way to grab the kind of control I'm looking for? Do I need to create a stateful bean that is aware of the count per exception? Or am I overlooking something simple? I want to make sure that as I approach this refactor I don't start us off on an ugly path.
Using Camel 2.10.1

I checked your case with following test:
import org.apache.camel.EndpointInject;
import org.apache.camel.Exchange;
import org.apache.camel.Produce;
import org.apache.camel.ProducerTemplate;
import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.component.mock.MockEndpoint;
import org.apache.camel.test.junit4.CamelTestSupport;
import org.junit.Test;
import java.util.concurrent.atomic.AtomicLong;
/**
* #author Illarion Kovalchuk
* Date: 12/7/12
* Time: 2:58 PM
*/
public class Test extends CamelTestSupport
{
private static final String MIDDLE_QUEUE = "seda:middle";
#EndpointInject(uri = "mock:result")
protected MockEndpoint resultEndpoint;
#Produce(uri = "direct:start")
protected ProducerTemplate template;
private Processor processor = new Processor();
#Test
public void shouldRedeliverOnErrors() throws Exception
{
resultEndpoint.expectedBodiesReceived("Body");
template.sendBodyAndHeader(MIDDLE_QUEUE, "Body", "Header", "HV");
resultEndpoint.assertIsNotSatisfied();
}
#Override
protected RouteBuilder createRouteBuilder()
{
return new RouteBuilder()
{
#Override
public void configure() throws Exception
{
onException(FooException.class)
.redeliveryDelay(2000)
.maximumRedeliveries(10);
onException(BarException.class)
.redeliveryDelay(5000)
.maximumRedeliveries(2);
from(MIDDLE_QUEUE)
.bean(Processor.class, "process")
.to(resultEndpoint)
.end();
}
};
}
public static class Processor
{
private static AtomicLong retryState = new AtomicLong(0L);
public static void process(Exchange e) throws FooException, BarException
{
long rs = retryState.getAndAdd(1L);
if (rs < 4)
{
System.err.println("Foo Attempt "+ rs);
throw new FooException();
}
if (rs == 4)
{
System.err.println("Bar Attempt "+ rs);
throw new BarException();
}
System.err.println("Normal Attempt "+ rs);
}
}
public static class FooException extends Throwable
{
}
private static class BarException extends Throwable
{
}
}
As the result, your concirn was approved: delivery attempts gets exhausted after BarException, even if we have only 4 FooExceptions and 1 BarException.
Unfortunately I can't answer your question fully right now, but I am digging into it and will updated my unswer if get something new.

Try to replace the order you define your exceptions, e.g.:
<onException>
<exception>my.exception.BarException</exception>
<redeliveryPolicy maximumRedeliveries="2" redeliveryDelay="5000"
</onException>
<onException>
<exception>my.exception.FooException</exception>
<redeliveryPolicy maximumRedeliveries="10" redeliveryDelay="2000"
</onException>

Related

Mock a void method

//Original method:
#Autowired
private ConversionServiceValidator validator;
public CRSConversionResult convertCRS(ConvertCrsVo convertCrsVo) throws Exception {
if (validator.isSameSourceAndTarget(convertCrsVo))
throw new ValidationException(Constants.BADREQUEST);
if (convertCrsVo.getPreferredTransforms() != null) {
List<TransformVo> preferredTransformList = new ArrayList<>();
for (TransformVo transformVo : convertCrsVo.getPreferredTransforms()) {
preferredTransformList.add(getPerfByCode(transformVo));
}
convertCrsVo.setPreferredTransforms(preferredTransformList);
}
convertCrsVo.setSourceCRS(getCrsVoByCode(convertCrsVo.getSourceCRS()));
convertCrsVo.setTargetCRS(getCrsVoByCode(convertCrsVo.getTargetCRS()));
convertCrsVo = validator.replaceCoordinates(convertCrsVo);
logger.info("ShellGeodeticService::convertCRS::Request to GeoCalService convertpoints::" + mapper.writeValueAsString(convertCrsVo));
ConvertPointsResponse response = geoCalService.convertCRS(convertCrsVo);
CRSConversionResult result = new CRSConversionResult();
result.setCriteriaMessage(response.getCriteriaMessage());
result.setResultPoints(response.getResultPoints());
result.setTransformName(response.getTransformName());
result.setTransformDescription(response.getTransformDescription());
// added schema as per pbi 195298
List<ConvertedTransformsResult> transformsResults = new ArrayList<>();
if (response.getTransforms() != null || !response.getTransforms().isEmpty())
response.getTransforms().stream().forEach(
t -> transformsResults.add(new ConvertedTransformsResult().getConvertedTransformsResult(t)));
result.setTransforms(transformsResults);
String logmessage=generateLogMessage(result,convertCrsVo);
logger.info(logmessage);
validator.isResponseValid(result);
return result;
}
//The testcase for the above method
#Test
public void testconvertCRSJob() throws Exception{
ConvertCrsVo convertCrsVo = TestDataFactory.getConvertCrsVo();
CRSConversionResult crsConversionResult = TestDataFactory.getCRSConversionResult();
ConversionServiceValidator conversionServiceValidatorMock = mock(ConversionServiceValidator.class);
Mockito.when(geoCalService.convertCRS(Mockito.any()))
.thenReturn(TestDataFactory.getConvertPointsResponse(convertCrsVo));
Mockito.when(validator.replaceCoordinates(convertCrsVo))
.thenReturn(TestDataFactory.getConvertCrsVo());
Mockito.when(geoCalService.search(Mockito.any(SearchFilter.class)))
.thenReturn(TestDataFactory.getSearchResultResponseForCRS());
Mockito.when(shellGeodeticService.convertCRS(convertCrsVo))
.thenReturn(TestDataFactory.getCRSConversionResult());
shellGeodeticService.convertCRSJob();
}
The error that i am getting is as below:
org.mockito.exceptions.misusing.CannotStubVoidMethodWithReturnValue:
'isResponseValid' is a void method and it cannot be stubbed with a return value!
Voids are usually stubbed with Throwables:
doThrow(exception).when(mock).someVoidMethod();
If you're unsure why you're getting above error read on.
Due to the nature of the syntax above problem might occur because:
1. The method you are trying to stub is overloaded. Make sure you are calling the right overloaded version.
2. Somewhere in your test you are stubbing final methods. Sorry, Mockito does not verify/stub final methods.
3. A spy is stubbed using when(spy.foo()).then() syntax. It is safer to stub spies -
- with doReturn|Throw() family of methods. More in javadocs for Mockito.spy() method.
4. Mocking methods declared on non-public parent classes is not supported.
at com.shell.geodetic.GeodeticConvertionApiAppTests.testconvertCRSJob(GeodeticConvertionApiAppTests.java:1783)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
Can someone help me on how to stub the void method "isResponseValid" ? I tried around 100 combinations that i saw in SOF and nothing worked. Thanks for the help in advance.
*Edit
Class ConversionServiceValidator {
public void isResponseValid(CRSConversionResult response) throws InvalidDataException {
if (response.getResultPoints().isEmpty() || response.getResultPoints() == null) {
throw new ValidationException("Request body has incorrect format");
} else {
for (Point point : response.getResultPoints()) {
if (point.getX().trim().equals("0") || point.getY().trim().equals("0")) {
throw new InvalidDataException(400, "Bad Request", "WARNING: Not all points could be converted",
response);
}
}
}
It is a mock #InjectMocks ShellGeodeticService shellGeodeticService;
shellGeodeticService is not a mock. #InjectMocks is used for the class under test, where the mocks are injected into.
That implies you can not use
Mockito.when(shellGeodeticService.convertCRS(convertCrsVo))
.thenReturn(TestDataFactory.getCRSConversionResult());
in your test as only mocks(or spys) can be used within Mockito.when.
Actually im trying to run test case for shellGeodeticService.convertCRS() and since it calls isResponseValid method internally , i have to mock that also right?
No, that is incorrect. If validator is a mock every method invocation will do nothing by default. So, unless you want to throw an exception, you do not need to define anything.
As your question lacks some details, I assume a complete version of your test could be similiar to this:
#InjectMocks
ShellGeodeticService shellGeodeticService;
#Mock
ConversionServiceValidator validator;
#Mock
... geoCalService; // some unknown class
#Test
public void testconvertCRSJob() throws Exception{
ConvertCrsVo convertCrsVo = TestDataFactory.getConvertCrsVo();
// Note sure whether this is correct by your logic as there is no `replacement` happening.
Mockito.when(validator.replaceCoordinates(convertCrsVo)).thenReturn(convertCrsVo);
Mockito.when(geoCalService.convertCRS(Mockito.any())).thenReturn(TestDataFactory.getConvertPointsResponse(convertCrsVo));
CRSConversionResult result = shellGeodeticService.convertCRS();
// do some assertions on the result
}
As validator is a mock:
validator.isSameSourceAndTarget(convertCrsVo) returns false be default
validator.isResponseValid( ... ) does nothing by default
As you did not add the methods getCrsVoByCode, getPerfByCode and generateLogMessage take note that if there are any further interactions with the mocked objects you'll need to add them.
(eg.: a call to geoCalService.search is not visible in your test code, so I removed the behaviour definition from the test displayed above)

Ignoring invoking internal static call

public static ResponseBean call(Bean bean) throws Exception {
// statements...
IgnoreCall.ignoreMethodCall(bean);
// statements...
// return
}
With the code snippet above, is it possible to test the method ignoring invocation of IgnoreCall.ignoreMethod(Bean) without needing to place the entire statement under a boolean condition?
Here's the unit test code snippet:
#RunWith(PowerMockRunner.class)
#PrepareTest
public ClassHelperTest {
#Test
public void testCall() throws Excpetion {
// stubbing...
ResponseBean responseBean = ClassHelper.call(bean);
// verify/ies
// assert/s
}
}
Notes:
Refactoring ClassHelper.call(Bean) should be avoided. Even with a bad OO design, refactoring is costly.
Method signature is locked unless another pattern is applicable for replacement.
Tried using Mockito.when and PowerMockito.when on the target static method, stubbing didn't work on run-time debug.
As your comments indicate that changing your production code is not possible, you "simply" have to dive into the static-mocking aspects of PowerMock; as outlined here for example.
Basically you need to enable IgnoreCall for static mocking; and then you make calls to ignoreMethodCall() a no-op.
But as you keep asking: the core problem with your question is the fact that you want to mock out a static method that is void. I have a complete example below, but before that some explanations.
The point is: you call a method for two reasons:
It has a side effect
It returns a value, and maybe, causes a side effect, too
A void method can only be called for side effects. And the thing is: when you do static mocking, then that works on class level.
Meaning: you instruct PowerMock to "prevent" any of the static methods of some class from execution; you simply "erase" the side effects of all those static methods! So, by telling PowerMock to do those static mocks, all void methods are already "gone".
But as said, you might also call methods for their return value. And then is when the when() method of Mockito kicks in. You use that method to say: when that value-returning method is invoked, then do this or that.
Long story short; here is a [mcve] using the elements you asked for:
package ghostcat.test;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
class IgnoreCall {
public static void ignoreMethodCall(Object o) {
System.out.println("SHOULD NOT SHOW UP: " + o);
}
}
class CuT {
public static Object call(Object bean) {
System.out.println("statement1");
IgnoreCall.ignoreMethodCall(bean);
System.out.println("statement2");
return "whatever";
}
}
#RunWith(PowerMockRunner.class)
#PrepareForTest(IgnoreCall.class)
public class PMTest {
#Test
public void test() {
PowerMockito.mockStatic(IgnoreCall.class);
CuT.call("yeha");
}
}
As in your example ... there is IgnoreCall; used within that a static method that I just called "call".
This prints:
statement1
statement2
When I go in and comment out
// PowerMockito.mockStatic(IgnoreCall.class);
It prints:
statement1
SHOULD NOT SHOW UP: yeha
statement2
So, a simple example that should tell you exactly what you need to do.
I worked with eclipse neon, IBM java8 JDK, and simply imported all the JARs from powermock-mockito-junit-1.6.6.zip into my test project.

Unable to mock URL class using PowerMockito/Mockito

I am trying to use PowerMockito to mock the creation of the java.net.URL class in my code that I'm testing. Basically, I want to prevent the real HTTP request from occurring and instead 1) check the data when the request is made and 2) supply my own test data back on a mocked response. This is what I'm trying:
#RunWith(PowerMockRunner.class)
#PrepareForTest({ URL.class, MockedHttpConnection.class })
public class Test {
URL mockedURL = PowerMockito.mock(URL.class);
MockedHttpConnection mockedConnection = PowerMockito.mock(MockedHttpConnection.class);
...
PowerMockito.whenNew(URL.class).withParameterTypes(String.class).withArguments("MyURLString").thenReturn(mockedURL);
PowerMockito.when(mockedURL.openConnection()).thenReturn(mockedConnection);
...
}
The code that I want to test looks like this:
URL wlInvokeUrl = new URL(wlInvokeUrlString);
connection = (HttpURLConnection) wlInvokeUrl.openConnection();
Earlier in my test scenario I mock the wlInvokeUrlString to match "MyURLString". I've also tried using various other forms of the whenNew line, trying to inject the mock. No matter what I try, it never intercepts the constructor. All I want to do is "catch" the call to openConnection() and have it return my mocked HTTP connection instead of the real one.
I have mocked other classes ahead of this one in the same script and these are working as expected. Either I need a second pair of eyes (probably true) or there is something unique about the URL class. I did notice that if I use "whenNew(URL.class).withAnyArguments()" and change the "thenReturn" to "thenAnswer" I could get it to trigger. Only problem is I never get the URL call for my code. What I see is an invocation of the 3-argument constructor for URL.class with all nulls for the parameters. Could it be this class is from the Java runtime and is bootstrapped by the test runner? Any help is much appreciated.
It's a common mistake when use PowerMockito.whenNew.
Note that you must prepare the class creating the new instance of MyClass for test, not the MyClass itself. E.g. if the class doing new MyClass() is called X then you'd have to do #PrepareForTest(X.class) in order for whenNew to work
From Powermock wiki
So, you need a bit change your test and add to #PrepareForTesta class which create a new instance of URLlike:
#RunWith(PowerMockRunner.class)
#PrepareForTest({ URL.class, MockedHttpConnection.class , ConnectionUser.class})
public class URLTest {
public class URLTest {
private ConnectionUser connectionUser;
#Before
public void setUp() throws Exception {
connectionUser = new ConnectionUser();
}
#Test
public void testName() throws Exception {
URL mockedURL = PowerMockito.mock(URL.class);
MockedHttpConnection mockedConnection = PowerMockito.mock(MockedHttpConnection.class);
PowerMockito.whenNew(URL.class).withParameterTypes(String.class).withArguments("MyURLString").thenReturn(mockedURL);
PowerMockito.when(mockedURL.openConnection()).thenReturn(mockedConnection);
connectionUser.open();
assertEquals(mockedConnection, connectionUser.getConnection());
}
}
where:
public class ConnectionUser {
private String wlInvokeUrlString = "MyURLString";
private HttpURLConnection connection;
public void open() throws IOException {
URL wlInvokeUrl = new URL(wlInvokeUrlString);
connection = (HttpURLConnection) wlInvokeUrl.openConnection();
}
public HttpURLConnection getConnection() {
return connection;
}
}
I'm not sure the difference between .withParameterTypes(x) and .withArguments(x) but I believe you need to set it up as follows for your code to work. Give it a try and let me know if this doesn't help.
PowerMockito.when(mockedURL.openConnection()).thenReturn(mockedConnection);
PowerMockito.whenNew(URL.class).withArguments(Mockito.anyString()).thenReturn(mockedURL);
The problem is that the arguments of the real call are not matching with the expected in your mock.
PowerMockito.whenNew(URL.class).withParameterTypes(String.class).withArguments("MyURLString").thenReturn(mockedURL) will return mockedURL only the call is new URL("MyURLString").
If you change it to:
PowerMockito.whenNew( URL.class ).withParameterTypes( String.class )
.withArguments( org.mockito.Matchers.any( String.class ) ).thenReturn( mockedURL );
It will catch any string passed to the constructor URL(String) (even null) and return your mock.
When you tried
"whenNew(URL.class).withAnyArguments()" and change the "thenReturn" to
"thenAnswer" I could get it to trigger. Only problem is I never get
the URL call for my code. What I see is an invocation of the
3-argument constructor for URL.class with all nulls for the
parameters.
PowerMock will try to mock all constructors (org.powermock.api.mockito.internal.expectation.DelegatingToConstructorsOngoingStubbing.InvokeStubMethod at line 122) then it calls the first one (with 3 arguments) and mock its answer. But the subsequent calls will return the already mocked one because you told it to mock for any arguments. That's why you see just one call with null, null, null in your Answer.

How to test loop inside loop in mockito

Hi i have method insertOrUpdateProductsToDB(Product product) is used to perform insert operation in database using catalogService of Broadleaf ,catalog Service is doing all saving operation in db . My method is expected restClient product as a parameter.After passing the restClient product we are converting this product into Broadleafproduct by using ProductConversion Class.In product conversion only setting is happening for converting rest Product into broadleafproduct. Now my requirement is to test this method using mockito but when i tried to do add these two line at the end of my test method
verify(mainProduct).getAdditionalSkus().add(sku);
verify(mainProduct).setProductOptions(productOptionList);
Its failing.
when i debug the code there is for loop inside for loop in the method insertOrUpdateProductsToDB(Product product) and i find productOption = catalogService.saveProductOption(productOption); here productOption is coming null so please tell how to test loop inside loop and same happening for
for (Sku skuWithProductOptions : productConversion.createSkuWithProductOptions(product, mainProduct,productOptionList)) {
catalogService.saveSku(skuWithProductOptions);
}
this line in the same method .kindly also check my test case whether i am doing right or not .
Class and insertOrUpdateProductsToDB(Product product) Method to be test
import com.admin.exception.AdminGenericException;
import com.admin.exception.AdminRestException;
import com.admin.util.helper.ProductConversion;
import com.admin.wrapper.getproducts.req.ObjectFactory;
import com.admin.wrapper.getproducts.resp.Product;
import com.admin.wrapper.getproducts.resp.Response;
import com.mycompany.rest.service.client.RestClientUtil;
import com.mycompany.util.constants.ApplicationConstants;
#Service
public class GetProductsServiceImpl {
private static final Logger logger = Logger.getLogger(GetProductsServiceImpl.class);
#Resource(name = "blCatalogService")
protected CatalogService catalogService;
public void setCatalogService(CatalogService catalogService) {
this.catalogService = catalogService;
}
protected RestClientUtil restClientUtil;
public void setRestClientUtil(RestClientUtil restClientUtil) {
this.restClientUtil = restClientUtil;
}
#Value("#{configProperties['salePriceRate']}")
private long salePriceRate;
public void setRetailPriceRate(long retailPriceRate) {
this.retailPriceRate = retailPriceRate;
}
#Value("#{configProperties['retailPriceRate']}")
private long retailPriceRate;
public void setSalePriceRate(long salePriceRate) {
this.salePriceRate = salePriceRate;
}
//Insertion/Update DB logic
public String insertOrUpdateProductsToDB(Product product) {
logger.debug("Start of : insertOrUpdateProductsToDB()");
try {
List<String> category = new ArrayList<String> (Arrays.asList(ApplicationConstants.CATEGORY));
ProductConversion productConversion = new ProductConversion();
List<ProductOption> productOptionList = new ArrayList<ProductOption>();
if (category.contains(product.getCategory().toUpperCase())) {
org.broadleafcommerce.core.catalog.domain.Product mainProduct=catalogService.createProduct(new ProductType("org.broadleafcommerce.core.catalog.domain.Product", "Normal Product"));
mainProduct = productConversion.createProduct(mainProduct,product);
Sku sku=catalogService.createSku();
mainProduct.setDefaultSku(sku);
mainProduct = productConversion.addSkuToProduct(mainProduct, product, salePriceRate,retailPriceRate);
for (ProductOption productOption : productConversion.createProductOptions(product, mainProduct)) {
productOption.setAllowedValues(productConversion.createProductOptionValues(product,productOption));
productOption = catalogService.saveProductOption(productOption);
productOptionList.add(productOption);
}
sku = catalogService.saveSku(mainProduct.getDefaultSku());
mainProduct.getAdditionalSkus().add(sku);
mainProduct.setProductOptions(productOptionList);
mainProduct = catalogService.saveProduct(mainProduct);
for (Sku skuWithProductOptions : productConversion.createSkuWithProductOptions(product, mainProduct,productOptionList)) {
catalogService.saveSku(skuWithProductOptions);
}
}
logger.debug("End of : insertOrUpdateProductsToDB()");
return "Product inserted into DB successfully";
}
catch (Exception e) {
logger.error("Error:", e);
return "Insertion of product into DB Failed ";
}
}
//Insertion service for DB
public String insertProductsIntoDB(){
logger.debug("Start of : insertProductsIntoDB()");
int insertionCount=0;
try{
com.admin.wrapper.getproducts.resp.Response resp = getAvailableProductsFromPBS();
for (Product product : resp.getProducts().getProduct()) {
if(catalogService.findProductById(Long.parseLong(product.getId()))==null){
String str=insertOrUpdateProductsToDB(product);
if(str.equalsIgnoreCase("Product inserted into DB successfully")){
insertionCount=insertionCount+1;
}
}
}
logger.debug(insertionCount+" Products inserted into DB successfully");
logger.debug("End of : insertProductsIntoDB()");
return insertionCount+" Products inserted into DB successfully";
}catch (AdminRestException e) {
logger.error("Error:", e);
return e.getMessage();
}
}
}
My test case class and method
public class GetProductsServiceImplTest {
private CatalogService catalogService;
private RestClientUtil restClientUtil;
private GetProductsServiceImpl getProductsServiceImpl;
private org.broadleafcommerce.core.catalog.domain.Product mainProduct;
private Sku sku;
private ProductOption productOption;
private List<ProductOption> productOptionList;
#Before
public void setUp() throws Exception {
catalogService = mock(CatalogService.class);
productOptionList=mock(List.class);
mainProduct = spy(new ProductImpl());
sku = new SkuImpl();
getProductsServiceImpl = new GetProductsServiceImpl();
getProductsServiceImpl.setCatalogService(catalogService);
productOption=mock(ProductOption.class);
restClientUtil = new RestClientUtil();
}
#Test
public void testInsertOrUpdateProductsToDB() {
restClientUtil.setSellerCode("1");
restClientUtil.setPbsUrl("http://10.52.165.239:8080/pbs");
getProductsServiceImpl.setRestClientUtil(restClientUtil);
Response pbsResponse = getProductsServiceImpl
.getAvailableProductsFromPBS();
for (Product pbsProduct : pbsResponse.getProducts().getProduct()) {
when(catalogService.createProduct(new ProductType("org.broadleafcommerce.core.catalog.domain.Product","Normal Product"))).thenReturn(mainProduct);
when(catalogService.createSku()).thenReturn(sku);
when(catalogService.saveProductOption(productOption)).thenReturn(productOption);
when(catalogService.saveSku(sku)).thenReturn(sku);
when(catalogService.saveProduct(mainProduct)).thenReturn(mainProduct);
when(catalogService.saveSku(sku)).thenReturn(sku);
getProductsServiceImpl.insertOrUpdateProductsToDB(pbsProduct);
verify(mainProduct,times(2)).setDefaultSku(sku);
verify(mainProduct).getAdditionalSkus().add(sku);
verify(mainProduct).setProductOptions(productOptionList);
break;
}
}
}
This is the error while testing
java.lang.NullPointerException
at com.admin.api.service.getproducts.test.GetProductsServiceImplTest.testInsertOrUpdateProductsToDB(GetProductsServiceImplTest.java:68)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:606)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:47)
I have a few remarks that probably won't answer your orignal question. But I hope they will guide you toward a better refactor of this code. Also the code sample you showed are not enough to point you at the exact issue ; it's an NPE in the test method so it should not be that difficult to track down.
That being said here's the point I'd like to raise
The test code is curiously crafted, and in my opinion this code are overusing Mockito. Overall this code looks way too complex to be correctly tested anyway. I don't think it was coded following TDD principle (TDD is really convenient when it comes to testing and designing the app)
You may want to follow the common guideline no more than 10 line of codes in a single method, this usually helps to separate concerns and identify simpler code / intents. These simpler code could be changed and tested more easily if designed correctly (without leaking concepts or variables). For example you may want to extract a method that saves a single Product and test only that one.
What's even more striking is that this code seems kinda procedural (even if inside objects). And doesn't really explain the intent in business words (ok it's about saving stuff in DB, but for which reason there's all this logic, this reason should appear in the method name).
The test and Mockito is weird, and the code should not iterate over the collection to stub then verify
for (Product pbsProduct : pbsResponse.getProducts().getProduct()) {
when(catalogService.createProduct(new ProductType("org.broadleafcommerce.core.catalog.domain.Product","Normal Product"))).thenReturn(mainProduct);
when(catalogService.createSku()).thenReturn(sku);
when(catalogService.saveProductOption(productOption)).thenReturn(productOption);
when(catalogService.saveSku(sku)).thenReturn(sku);
when(catalogService.saveProduct(mainProduct)).thenReturn(mainProduct);
when(catalogService.saveSku(sku)).thenReturn(sku);
getProductsServiceImpl.insertOrUpdateProductsToDB(pbsProduct);
verify(mainProduct,times(2)).setDefaultSku(sku);
verify(mainProduct).getAdditionalSkus().add(sku);
verify(mainProduct).setProductOptions(productOptionList);
break;
}
In pseudo code I would first try to extract the saving logic using the given/when/then BBDD keywords (they help to clarify what need to be tested in which scenario and context). Keep the fixture and assertions to a minimum, you would rather deal with multiple test method than multiple complex test methods.
#Test
public void ensure_product_is_saved_in_the_catalog() {
// given
Product a_simple_product = ProductBuilder.simpleProduct().build();
when(catalogService.doSomething(....))).thenReturn(mainProduct);
// when
productsService.saveProduct(product);
// then
verify(catalogService).doSomethingElseWith(mainProduct);
}
If assertion on product data is relevant in your test scenario, then write a test that actually test the data (using JUnit assertions, AssertJ, ...). Don't mock the Product !
And proceed gradually for each test, then refactor if need ed to keep the code manageable (extract a single method in another class if necessary, etc.)
You should definitely read the following books, they've helped a lot of programmers to get better code Clean Coder or Growing Object Oriented Software, Guided by Tests. This list is of course not exhaustive.
Hope that helps.

Camel Exception handling doesnt work if exception clause is defined in a separate class

I am trying to build a application with several camel routes which re use many common routes internally.
Hence, I am trying to segregate the routes in several different Route Builder classes and then connecting the routes where needed.
For eg, all routes pertaining to sending emails go into a EmailRouteBuilder class and all routes dealing with a particular JMS Queue go into MyQueueRouteBuilder class.
I suppose this should be alright since Camel doesnt not distinguish between classes and only looks for routes defininition.
In addition, I am also grouping several exception handling routes into a separate ExceptionHandlingRouteBuilder.
I am also connecting all the different classes together by defining the camel context in Spring like so -
<camelContext id="camelContext" xmlns="http://camel.apache.org/schema/spring">
<propertyPlaceholder id="properties" location="classpath:${env}/autoimport.properties"/>
<!-- Common Routes -->
<routeBuilder ref="emailRouteBuilder" />
<routeBuilder ref="myQueueRouteBuilder" />
<routeBuilder ref="httpRouteBuilder" />
<routeBuilder ref="exceptionsRouteBuilder" />
<routeBuilder ref="customer1RouteBuilder" />
<routeBuilder ref="customer2RouteBuilder" />
</camelContext>
My exceptionsRouteBuilder contains many exception clauses like -
onException(ConnectException.class)
.routeId("connectExceptionEP")
.handled(true)
.log("Caught Exception: ")
.to("direct:gracefulExit");
..
..
..
However, it looks like there is a problem with the exceptions being defined in another class, or for that matter, defined separately out of the main route definition.
I verified this in the logs by looking for the routes being booted ( by routeId ) and also checking when an exception is thrown.
Additionally, to further confirm, I took the http Connect Exception handling route and put that directly in the httpRouteBuilder and lo..! , the exception handling now kicks in just fine for this exception..
Am I missing something here to get all exceptions to work while being nicely defined in its own class. ?
I am using Apache Camel 2.9.0 , but I verified the same behavior also in 2.8.3.
Thanks,
Anand
correct, the onException() clauses only apply to the current RouteBuilder's route definitions...
that said, you can reuse these definitions by having all your RouteBuilders extend the ExceptionRouteBuilder and call super.configure()...something like this
public class MyRouteBuilder extends ExceptionRouteBuilder {
#Override
public void configure() throws Exception {
super.configure();
from("direct:start").throwException(new Exception("error"));
}
}
...
public class ExceptionRouteBuilder implements RouteBuilder {
#Override
public void configure() throws Exception {
onException(Exception.class).handled(true).to("mock:error");
}
}
or even just have a static method in an ExceptionBuilder class to setup the clauses for a given RouteBuilder instance
public class MyRouteBuilder extends RouteBuilder {
#Override
public void configure() throws Exception {
ExceptionBuilder.setup(this);
from("direct:start").throwException(new Exception("error"));
}
}
...
public class ExceptionBuilder {
public static void setup(RouteBuilder routeBuilder) {
routeBuilder.onException(Exception.class).handled(true).to("mock:error");
}
}
Based on the accepted answer, I found a cleaner way to implement exception handling, so you don't have to call super.configure() in every route. Just call a method that handles onException in the constructor of the base class.
//Base class that does exception handling
public abstracExceptionRouteBuildert class BaseAbstractRoute extends RouteBuilder {
protected BaseAbstractRoute() {
handleException();
}
private void handleException() {
onException(Exception.class).handled(true).to("mock:error");
}
}
//Extend the base class
public class MyRouteBuilder extends BaseAbstractRoute {
#Override
public void configure() throws Exception {
from("direct:start").throwException(new Exception("error"));
}
}