Consider below code. How can I test this without using third party libraries? The Assert line is never executed, because it is a different thread and the vm stops running. Many thanks!
public class FileParserTask extends AsyncTask<File, Void, ArrayList<City>> {
private FileParserResult mResult;
public interface FileParserResult {
void onFinish(ArrayList<City> cities);
}
public FileParserTask(final FileParserResult result) {
mResult = result;
}
#Override
protected ArrayList<City> doInBackground(File... files) {
ArrayList<City> cities = new ArrayList<>();
try {
InputStream is = new FileInputStream(files[0]);
JsonReader reader = new JsonReader(new InputStreamReader(is, "UTF-8"));
reader.beginArray();
while (reader.hasNext()) {
City city = new Gson().fromJson(reader, City.class);
cities.add(city);
}
reader.endArray();
reader.close();
} catch (Exception e) {
e.printStackTrace();
}
Collections.sort(cities, (o1, o2) -> o1.getName().compareTo(o2.getName()));
mResult.onFinish(cities);
return cities;
}
}
Test code:
#RunWith(AndroidJUnit4.class)
public class CityServiceTest {
File file = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), "cities-medium.json");
#Test
public void givenInputAbuThenIShouldGetXResults() throws InterruptedException {
new FileParserTask(cities -> {
Assert.assertEquals("Input should give back 200 results", 3, cities.size());
}).execute(file);
}
}
Although the code you need to test:
Assert.assertEquals("Input should give back 200 results", 3, cities.size());
is being run in an AsyncTask, that's not really relevant to unit testing. AsyncTask has most likely been extensively tested by Google so you know that it will work as an AsyncTask. The real testing seems to be the functionality that needs to be run in the background, i.e. the business logic contained in doInBackground.
Thinking about it in terms of business logic, there is a need to populate an ArrayList<City> and propagate it to the app. Android prefers this to be done on a background thread and propagation can be handled by notifications etc, both of which have been tested and released as working by Google so you don't really need to include them in a unit test. How you populate ArrayList<City> is the real unit test.
AsyncTask would be relevant for an integration test but you'd most likely be testing a different aspect of the app for that, i.e. what it displays rather than what it receives from a background thread.
So for a unit test I'd refactor out the code in doInBackground so that it can be tested independently of how Android wants it to be run.
Sorry, did you override the onPostExecute method of the AsyncTask.
You are keeping the Result handler, but not using it anywhere.
#Override
protected void onPostExecute(Object result) {
mResult.processFinish(result);
}
As for the assertion it looks good to me as it is.
As you say, the problem is the AsyncTask running in a background thread, via an ExecutorService. Like with a Future though, it provides a get() method that will wait for, and return, the result.
new FileParserTask(cities -> {
Assert.assertEquals("Input should give back 200 results", 3, cities.size());
}).execute(file).get();
Related
I recently developed few Verticles from which I needed to make external API calls. To optimize the code, I moved code of calling APIs to one common Helper class. I am also passing Vertx instance from Verticle to Helper class. I am now trying to write Junit test case for the Helper class which is looking like below working code.
public class ServiceExecutionHelper{
public Promise<String> executeService(String requestURI, JsonObject input, MultiMap headers, Vertx vertx){
Promise<String> promise = Promise.promise();
WebClient client = WebClient.create(vertx);
client.postAbs(requestURI).timeout(60000).putHeaders(headers)
.sendJsonObject(input, ar -> {
if (ar.succeeded()) {
HttpResponse<Buffer> response = ar.result();
JsonObject serviceRespone = new JsonObject(response.bodyAsString());
JsonArray responseData = serviceRespone.getJsonArray("response_data");
if(responseData != null){
promise.complete("promise_completed");
}else{
promise.fail("promise_failed");
}
}
}
return promise;
}
}
Can anyone please guide how could I write test case for above code?
There are a million ways to do this depending on what exactly you need to test.
Here is one suggestion using junit5 and okhttp's MockWebServer. There are a lot of other conceivable alternatives.
The test verifies:
That you send a POST request using the payload contained in the input parameter.
That your implementation can handle a json response from the webserver.
That your implementation sends exactly one request to the webserver.
That your code completes the Promise if the server's response contains the key "promise_completed"
#ExtendWith(VertxExtension.class)
#Slf4j
public class ServiceExecutionHelperTest {
private ServiceExecutionHelper sut;
private MockWebServer mockWebServer;
#BeforeEach
public void setUp() {
sut = new ServiceExecutionHelper();
mockWebServer = new MockWebServer();
}
#Test
public void testExecuteService(final Vertx vertx, final VertxTestContext testContext) throws InterruptedException {
// given
final JsonObject requestPayload = new JsonObject().put("request", new JsonArray("[]"));
final JsonObject serverResponsePayload = new JsonObject().put("response_data", new JsonArray("[]"));
mockWebServer.enqueue(new MockResponse()
.setBody(serverResponsePayload.encode())
.setResponseCode(200)
.setHeader("content-type", "application/json"));
// when
final Promise<String> stringPromise =
sut.executeService(
mockWebServer.url("/").toString(),
requestPayload,
MultiMap.caseInsensitiveMultiMap(),
vertx);
// then
final RecordedRequest recordedRequest = mockWebServer.takeRequest();
assertEquals("POST", recordedRequest.getMethod());
assertEquals("[text={\"request\":[]}]", recordedRequest.getBody().toString());
assertEquals(1, mockWebServer.getRequestCount());
testContext.assertComplete(stringPromise.future())
.map(val -> {
assertEquals("promise_completed", val);
testContext.completeNow();
return val;
})
.onComplete(onComplete -> {
assertTrue(onComplete.succeeded());
log.info("done");
})
.onFailure(onError -> Assertions.fail());
}
}
Some words from a TDD point of view
Before you start writing tests (and your actual code too, if you ask me), you should clarify your functional and technical requirements.
These should be the basis for your tests. And the tests should be a starting point to implement your code against.
So I cannot promise you that this example is a correct test for your use case. It compiles and and runs. But it should be verified and extended following your actual requirements.
Concerning test coverage
To keep this answer short and concise, I did not write the test to cover all possible branches. The case where the server responds without response_data (i.e. the else branch of your if-clause, where the Promise fails) is not tested.
To cover that case, a second test or the usage of a parameterized test would be necessary.
How to write a method in myDAOTest class to test my DAOImpl Class using Mockito? here is the method which i need to test from my TestClass.
#Override
public myCustomResponse mymethod(String query, Map<String, Object> parameters) {
jdbcTemplate.query(query, parameters, new ResultSetExtractor<List<MyObject>>() {
#Override
public List<MyObject> extractData(ResultSet result) throws SQLException, DataAccessException {
try {
List<MyObject> myObject= new ArrayList<>();
while (result.next()) {
response = getResponseDetails(result);
}
return myObject;
} catch (SQLException sqlException) {
LOG.debug(MyConstants.DATABASE_EXCEPTION_MESSAGE);
throw new MyCustomeException(MyConstants.DATABASE_EXCEPTION_MESSAGE);
}
}
});
}
return response;
}
Possibility 1:
Extract your ResultSetExtractor into it's own class, which makes it much simpler to test than as an anonymous inner class.
Possibility 2:
Mock the jdbcTemplate, create an ArgumentCaptor, call the method and then do...
Mockito.verify(this.jdbcTemplate).query(any(), any(), captor.capture());
This allows you to then get the ResultSetExtractor from the ArgumentCaptor and run tests on that. But this makes your whole test complex, so I still suggest possibility 1.
Possibility 3:
Test the whole thing against an in-memory database,testing the ResultSetExtractor only indirectly. Since this seems to be Spring, there are easy ways to do so, but of course, your unit test will now include a whole SpringApplicationContext, a db, etc. - which makes it slower than a "pure" unit test with only mocks. Since it's something that fires queries to a db, you will need to run it against a db anyway some time, otherwise your tests will not really be complete (simulating it via mocks is good for the basics, but not the same thing).
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.
I use Jersey and I have the following Rest function which returns a JSON string when my server is deployed:
#GET
#Path("getallemployees")
#Produces("application/json")
public Response getAllEmployees() {
//building the entity object which is List<Employee>
return Response.ok(entity).build();
}
I need to develop some unit tests (not integration testing) and I want to somehow mock the HTTPRequest that invokes this method and then get the json String. The best option would be to use mockito for this.
Is there any suggestion on how to do it ?
Thanks !!
The problem is that the method returns a Response object to the caller which is deep within the framework code. It doesn't return JSON strings.
You can use Mockito, if you need to mock something inside the method itself. That should work.
But you may need to take the value returned by the method and convert it to JSON like this if you are using Jackson with Jersey.
Response response = getAllEmployees();
Object retval = response.getEntity();
try {
ObjectMapper mapper = new ObjectMapper();
// I like this formatting. You can change it.
mapper.configure(Feature.INDENT_OUTPUT, true);
mapper.configure(Feature.WRITE_ENUMS_USING_TO_STRING, true);
mapper.configure(Feature.USE_ANNOTATIONS, false);
mapper.configure(Feature.FAIL_ON_EMPTY_BEANS, false);
mapper.setSerializationInclusion(Inclusion.NON_NULL);
mapper.getSerializationConfig().setSerializationInclusion(JsonSerialize.Inclusion.NON_NULL);
mapper.getSerializationConfig().withSerializationInclusion(JsonSerialize.Inclusion.NON_NULL);
String json = mapper.writeValueAsString(retval);
... assert something about the string
} catch (JsonProcessingException e) {
// do something
} catch (IOException e) {
// do something
}
Some of this is guess work and speculation on my part but it may help. You could try using the Jersey Test Framework with the InMemoryTestContainerFactory:
It starts Jersey application and directly calls internal APIs to handle request created by client provided by test framework. There is no network communication involved. This containers does not support servlet and other container dependent features, but it is a perfect choice for simple unit tests.
It looks like to use it, all you need to do is extend JerseyTest and then override getTestContainerFactory() and follow the rest of the instructions, e.g.:
public class EmployeeResourceTest extends JerseyTest {
#Override
protected Application configure() {
// set up employee resource with mock dependencies etc...
return new ResourceConfig().registerInstances(employeeResource);
}
#Test
public void getAllEmployees() {
final String response = target("getallemployees").request().get(String.class);
// assert etc...
}
}
I used registerInstances instead of registerClasses in configure() as it looks like you can present a ready made Resource but set up with any mock dependencies you may want - although I haven't tried this myself.
The test class is a bit inflexible as you can only do one-time set up of dependencies in the configure() method, so it might be worth investigating using the MockitoJUnitRunner - although I'm not sure if it will work with the JerseyTest inheritance. It could allow you to do add behaviour to mocks in each #Test method, e.g.:
#Mock
private EmployeeResourceDependency dependency;
#InjectMocks
private EmployeeResource employeeResource;
// configure() as above but without mock setup up etc...
#Test
public void getAllEmployees() {
given(dependency.getEmployees()).willReturn(...);
// etc...
But like I said it might not be possible to mix them at all.
I am getting time out from using JsonpRequestBuilder.
The entry point code goes like this:
// private static final String SERVER_URL = "http://localhost:8094/data/view/";
private static final String SERVER_URL = "http://www.google.com/calendar/feeds/developer-calendar#google.com/public/full?alt=json-in-script&callback=insertAgenda&orderby=starttime&max-results=15&singleevents=true&sortorder=ascending&futureevents=true";
private static final String SERVER_ERROR = "An error occurred while "
+ "attempting to contact the server. Please check your network "
+ "connection and try again.";
/**
* This is the entry point method.
*/
public void onModuleLoad() {
JsonpRequestBuilder requestBuilder = new JsonpRequestBuilder();
// requestBuilder.setTimeout(10000);
requestBuilder.requestObject(SERVER_URL, new Jazz10RequestCallback());
}
class Jazz10RequestCallback implements AsyncCallback<Article> {
#Override
public void onFailure(Throwable caught) {
Window.alert("Failed to send the message: " + caught.getMessage());
}
#Override
public void onSuccess(Article result) {
// TODO Auto-generated method stub
Window.alert(result.toString());
}
The article class is simply:
import com.google.gwt.core.client.JavaScriptObject;
public class Article extends JavaScriptObject {
protected Article() {};
}
The gwt page, however, always hit the onFailure() callback and show this alert:
Failed to send the message. Timeout while calling <url>.
Fail to see anything on the Eclipse plugin console. I tried the url and it works perfectly.
Would appreciate any tip on debugging technique or suggestion
Maybe you should set the callback function explicitly via setCallbackParam, since you have callback=insertAgenda in your url - I presume that informs the server what should be the name of the callback function that wraps the JSON.
Also, it's worth checking Firebug's console (or a similar tool for your browser) - even if GWT doesn't report any exceptions, Firebug still might.
PS: It's useful to use a tool like Firebug to see if the application does in fact receive the response from the server (that would mean that, for example, you do need the setCallbackParam call) or maybe there's something wrong on the server side (for whatever reason).
You have to read the callback request-Parameter (default callback, value something like __gwt_jsonp__.P0.onSuccess) on serversite and have to modify the output to
<callback>(<json>);
In this case:
__gwt_jsonp__.P0.onSuccess(<json>);
Both of these guys are absolutely correct, but here is a concrete example to help you understand exactly what they are referring too.
This is a public JSON api. Take a look at the results:
http://ws.geonames.org/postalCodeLookupJSON?postalcode=M1&country=GB&maxRows=4
This public API supports JSONP through the predefined parameter 'callback'. Basically whatever value you pass into callback, will be used as the function name to wrap around the JSON data you desire. Take a look at the results of these few requests:
http://ws.geonames.org/postalCodeLookupJSON?postalcode=M1&country=GB&maxRows=4&callback=totallyMadeUp
http://ws.geonames.org/postalCodeLookupJSON?postalcode=M1&country=GB&maxRows=4&callback=trollingWithJSONP
It could be happening because of another reason, that the webservice call is returning a JSON object and but the callback is expecting JSONP object (note there is a difference).
So if you are dealing with google maps api, and you are seeing this exception, you need to change it to api provide by maps api, something like
final GeocoderRequest request = GeocoderRequest.create();
request.setAddress(query);
try {
GWT.log("sending GeoCoderRequest");
if (m_geocoder == null) {
m_geocoder = Geocoder.create();
}
m_geocoder.geocode(request, new Geocoder.Callback() {
#Override
public void handle(final JsArray<GeocoderResult> results,
final GeocoderStatus status) {
handleSuccess(results, status);
}
});
} catch (final Exception ex) {
GWT.log("GeoCoder", ex);
}
Or else you could use RequestBuilder as in gwt library.