I want to write mockito for the following code , but not getting how to write for method inside method buildGetSubtenantsURL ,getSubtenants,getSubtenantName -
public void addNewMap(MapDTO mapDTO) {
`...............................`
String subtenantsURL = buildGetSubtenantsURL(null);
String subTenantsResponse = getSubtenants(subtenantsURL,iottenant);
JSONObject subTenant = getSubtenantName(subTenantsResponse);
checkForMultiplePagesSubtenants(subTenantsResponse, subtenantInfoMap,iottenant);
if(subtenantInfoMap.get(mapDTO.getSubtenantName()) != null) {
mapEntity = Maps.builder().subtenant(subtenantInfoMap.get(mapDTO.getSubtenantName()).toString()).build();
}
else {
throw new DataNotFoundException(SUBTENANT_DOESNT_EXIST);
}
String SubtenantId = subtenantInfoMap.get(mapDTO.getSubtenantName());
UriComponents assetsURL = buildGetAssetsURL(iottenant,SubtenantId);
String assetsResponse = getAssets(assetsURL, iottenant);
String mindsphereAssetId = getAssetId(assetsResponse);
}
String url = new StringBuilder().append(mindsphereBaseURL).append(mindsphereAssetsURL).toString();
UriComponents baseUriComponents = UriComponentsBuilder.fromHttpUrl(url).build();
JSONObject typeId = new JSONObject();
typeId.put(Constants.TYPEID, iottenant + "." + assetType);
typeId.put(Constants.SUBTENANT,SubtenantId);
baseUriComponents = UriComponentsBuilder.fromUri(baseUriComponents.toUri())
.queryParam(Constants.FILTER, typeId.toString()).queryParam(Constants.BASIC_FIELDS_ONLY,"true").build().encode();
return baseUriComponents;
You can't achieve this with Mockito. For that, you would need Powermock (check https://www.baeldung.com/powermock-private-method for details and examples).
Having said that, mocking private methods is a really bad practice. By doing so what will you be testing? Will this be a partial unit test (since you are not even testing the whole unit, which is the class under test)? My suggestion is that you avoid this as harder as you can.
Related
I have built a Rally dependency, which auto creates test case, folder in Test Plan. While creating test case it checks first if there any any existing test case with same name, else it creates new test case.
This was working while total test case size was small, while the test case size increased, i am seeing duplicate test cases are created. So I made thread to wait for few seconds (Thread.sleep(8000)) after checking existing scenarios and then creating new scenario. It works by this way.
Is there better way to handle & implement this to handle any size of test case. Please advice.
String tcName = rallyMethods.getTestScenarios(parentFolder, scenarioName);
Thread.sleep(8000);
if (tcName == null) {
rallyMethods.createTestCase(parentFolder, scenarioName);
Thread.sleep(8000);
} else {
rallyMethods.updateTestCase(parentFolder, scenarioName);
Thread.sleep(8000);
}
public String getTestScenarios(String parentFolderName, String ScenarioName) throws Throwable {
String sName = null;
String pFolder;
QueryRequest testCaseRequest = new QueryRequest("TestCase");
testCaseRequest.setLimit(Integer.MAX_VALUE);
testCaseRequest.setPageSize(Integer.MAX_VALUE);
testCaseRequest.setFetch(new Fetch("FormattedID", "Name", "Workspace", "Project", "TestFolder"));
testCaseRequest.setQueryFilter(new QueryFilter("Name", "=", ScenarioName));
testCaseRequest.setWorkspace(WORKSPACE_ID);
testCaseRequest.setProject(PROJECT_ID);
QueryResponse testCaseQueryResponse = query(testCaseRequest);
int testCaseCount = testCaseQueryResponse.getTotalResultCount();
// System.out.println("TestCaseCount:" + testCaseCount);
for (int i = 0; i < testCaseCount; i++) {
JsonObject scenarioObj = testCaseQueryResponse.getResults().get(i).getAsJsonObject();
String scenarioName = String.valueOf(scenarioObj.get("Name").getAsString());
JsonElement pFolderObj = scenarioObj.get("TestFolder");
if (!(pFolderObj.isJsonNull())) {
JsonObject tFolderObj = scenarioObj.get("TestFolder").getAsJsonObject();
pFolder = String.valueOf(tFolderObj.get("Name").getAsString());
if (parentFolderName.equalsIgnoreCase(pFolder)) {
sName = scenarioName;
logger.info("Test Scenarios identified in Rally: " + sName);
} else {
logger.info("Scenario, " + ScenarioName + " not found, New Scenario will be created in Rally");
}
}
}
return sName;
}
public void createTestCase(String parentFolderName, String testCaseName) throws Throwable {
String tcName = null;
String userID = readUser();
// Query Child Folders:
QueryRequest testFolderRequest = new QueryRequest("TestFolder");
testFolderRequest.setFetch(new Fetch("Name", "Workspace", "Project"));
testFolderRequest.setQueryFilter(new QueryFilter("Name", "=", parentFolderName));
testFolderRequest.setWorkspace(WORKSPACE_ID);
testFolderRequest.setProject(PROJECT_ID);
QueryResponse testFolderQueryResponse = query(testFolderRequest);
int folderCount = testFolderQueryResponse.getTotalResultCount();
for (int i = 0; i < folderCount; i++) {
String testFolderRef = testFolderQueryResponse.getResults().get(i).getAsJsonObject().get("_ref").getAsString();
JsonObject testFolderObj = testFolderQueryResponse.getResults().get(i).getAsJsonObject();
String pFolder = String.valueOf(testFolderObj.get("Name").getAsString());
if (pFolder.equalsIgnoreCase(parentFolderName)) {
//System.out.println("Creating a test case...");
JsonObject newTC = new JsonObject();
newTC.addProperty("Name", testCaseName);
newTC.addProperty("Workspace", WORKSPACE_ID);
newTC.addProperty("Project", PROJECT_ID);
newTC.addProperty("Description", "Selenium Automated TestCase");
newTC.addProperty("TestFolder", testFolderRef);
newTC.addProperty("Method", "Automated");
newTC.addProperty("Type", "Functional");
if (!(userID == null)) {
newTC.addProperty("Owner", userID);
}
CreateRequest createRequest = new CreateRequest("testcase", newTC);
CreateResponse createResponse = create(createRequest);
if (createResponse.wasSuccessful()) {
JsonObject tcObj = createResponse.getObject();
tcName = String.valueOf(tcObj.get("Name").getAsString());
logger.info("Created test scenario name is: " + tcName);
} else {
String[] createErrors;
createErrors = createResponse.getErrors();
logger.info("Error while creating test scenario below parent folder!");
for (int j = 0; j < createErrors.length; j++) {
System.out.println(createErrors[j]);
logger.info(createErrors[j]);
}
}
}
}
}
Hmmm... I'm not too familiar with the Java REST toolkit, but I can't think of a reason why a larger set of test cases in the workspace would cause the query to fail like that.
Did you try checking testCaseQueryResponse.wasSuccessful()? If it returns false, can you see what the error is? testCaseQueryResponse.getErrors()
My first thoughts are that you should provide a reasonable value for the limit and pageSize parameters, rather than passing Integer.MAX_VALUE. And second, rather than checking if the returned test cases are in the specified parent folder, you should include a query filter to filter the test cases results on TestFolder.Name = parentFolderName. Then you should only be expecting either 1 or 0 results returned (assuming that you're expecting all test cases within a test folder to have unique names).
I would like to test method which use another one? I tried do it using Mockito like below:
EDIT: Full method
public String createUrlAddress(TypeOfInformation typeOfInformation, String icao) {
switch (typeOfInformation) {
case METAR:
urlAddress = StaticValues.MAIN_URL_ADDRESS_FOR_METAR + icao;
break;
case TAF:
urlAddress = StaticValues.MAIN_URL_ADDRESS_FOR_TAF + icao + StaticValues.TAF_4_HOURS_BEFORE_NOW;
break;
case CITY_PAIR_METAR:
urlAddress = StaticValues.MAIN_URL_ADDRESS_FOR_CITY_PAIRS
+ pc.getDepartureAndArrivalTime().get("departureTime") //get departure time from hashmap
+ StaticValues.END_TIME_STRING
+ pc.getDepartureAndArrivalTime().get("arrivalTime")
+ StaticValues.STATION_STRING
+ pc.getOriginIcao()
+ ","
+ pc.getDestinationIcao()
+ StaticValues.MOST_RECENT_FOR_TYPED_STATIONS;
System.out.println(urlAddress);
break;
case CITY_PAIR_TAFS:
urlAddress = StaticValues.MAIN_URL_ADDRESS_FOR_CITY_PAIRS_TAFS
+ pc.getDepartureAndArrivalTime().get("departureTime")
+ StaticValues.END_TIME_STRING
+ pc.getDepartureAndArrivalTime().get("arrivalTime")
+ StaticValues.STATION_STRING
+ pc.getOriginIcao()
+ ",%20"
+ pc.getDestinationIcao()
+ StaticValues.MOST_RECENT_FOR_TYPED_STATIONS_TAFS;
System.out.println(urlAddress);
break;
default:
System.out.println("Wrong Type of informations");
}
return urlAddress;
}
Tests:
#Test
public void forGivenTypeOfInformationAndIcaoReturnUrl() {
HashMap<String,Long> departureAndArrivalTimeTest = new HashMap<>();
departureAndArrivalTimeTest.put("departureTime", 1499264449L);
departureAndArrivalTimeTest.put("arrivalTime", 1499282449L);
PageControllerForNearestCity pcSpy = Mockito.spy(pc);
Mockito.when(pcSpy.getDepartureAndArrivalTime()).thenReturn(departureAndArrivalTimeTest);
Mockito.when(pcSpy.getOriginIcao()).thenReturn("EPWA");
Mockito.when(pcSpy.getDestinationIcao()).thenReturn("EDDF");
assertThat(StaticValuesForTest.URL_ADDRESS_FOR_CITY_PAIR_METAR).isEqualTo(xmlParser.createUrlAddress(TypeOfInformation.CITY_PAIR_METAR, "EPGD")); }
How can I use my mocks in that case? Is it good approach or I have to do it in other way? I would like to add that I won't add these variables as arguments for this method.
PS I thought that the method has only one resposibility, just create a string, am I wrong? Should it be divided into another one like a "Service"?
Thank you for support
Your test enters too much in implementation details.
You mock the own processings/logic of your method. So it makes the test brittle and we can wonder what you assert really.
Besides, the test is complicated to read and to maintain.
At last, the processing associated to each case matters. It is the main logic of your method :
case CITY_PAIR_METAR:
urlAddress = StaticValues.MAIN_URL_ADDRESS_FOR_CITY_PAIRS
+ pc.getDepartureAndArrivalTime().get("departureTime") //get departure time from hashmap
+ StaticValues.END_TIME_STRING
+ pc.getDepartureAndArrivalTime().get("arrivalTime") //get arrival time from hashmap
+ StaticValues.STATION_STRING
+ pc.getOriginIcao()
+ ","
+ pc.getDestinationIcao()
+ StaticValues.MOST_RECENT_FOR_TYPED_STATIONS;
System.out.println(urlAddress);
It should be tested without mocking as you actually doing.
To do it, you should separate responsabilities by introducing a new class.
The actual class should only have a controller/dispatcher role and the new class should perform the logic with a public method by case.
In this way, you class under test could have a dependency on this class and you could mock them in a straight way.
Your actual method could finally look like :
private AddressService addressService;
public String createUrlAddress(TypeOfInformation typeOfInformation, String icao) {
switch (typeOfInformation) {
(...)
case CITY_PAIR_METAR:
urlAddress = addressService.createUrl();
break;
(...)
default:
System.out.println("Wrong Type of informations");
}
return urlAddress;
}
#rafaelim After your response I updated my test class and injected mock to the class like below:
#Before
public void setUp() throws Exception {
departureAndArrivalTimeTest = new HashMap<>();
xmlParser = new XmlParser();
pc = new PageControllerForNearestCity();
departureAndArrivalTimeTest.put("departureTime", 1499264449L); //second arg dep time in sec
departureAndArrivalTimeTest.put("arrivalTime", 1499282449L); //second arg arr time in sec
}
#Test
public void forGivenTypeOfInformationAndIcaoReturnUrl() {
PageControllerForNearestCity pcSpy = Mockito.spy(pc);
xmlParser.setPc(pcSpy);
Mockito.when(pcSpy.getDepartureAndArrivalTime()).thenReturn(departureAndArrivalTimeTest);
Mockito.when(pcSpy.getOriginIcao()).thenReturn("EPWA");
Mockito.when(pcSpy.getDestinationIcao()).thenReturn("EDDF");
assertThat(StaticValuesForTest.URL_ADDRESS_FOR_METAR).isEqualTo(xmlParser.createUrlAddress(TypeOfInformation.METAR, "EPGD"));
assertThat(StaticValuesForTest.URL_ADDRESS_FOR_TAF).isEqualTo(xmlParser.createUrlAddress(TypeOfInformation.TAF, "EPGD"));
assertThat(StaticValuesForTest.URL_ADDRESS_FOR_CITY_PAIR_METAR).isEqualTo(xmlParser.createUrlAddress(TypeOfInformation.CITY_PAIR_METAR, "EPGD"));
assertThat(StaticValuesForTest.URL_ADDRESS_FOR_CITY_PAIR_TAF).isEqualTo(xmlParser.createUrlAddress(TypeOfInformation.CITY_PAIR_TAFS, "EPGD"));
}
Test passed, but its a little bit unreadable, I have to work with "clean code" I think.
EDIT:
#davidxxx please look at this:
public class UrlAddressService {
PageControllerForNearestCity pc = new PageControllerForNearestCity();
public String createUrlForMetar() {
String urlAddressForMetars = new StringBuilder()
.append(StaticValues.MAIN_URL_ADDRESS_FOR_CITY_PAIRS)
.append(pc.getDepartureAndArrivalTime().get("departureTime"))
.append(StaticValues.END_TIME_STRING)
.append(pc.getDepartureAndArrivalTime().get("arrivalTime"))
.append(StaticValues.STATION_STRING)
.append(pc.getOriginIcao())
.append(",")
.append(pc.getDestinationIcao())
.append(StaticValues.MOST_RECENT_FOR_TYPED_STATIONS_METARS)
.toString();
return urlAddressForMetars;
}
public String createUrlForTaf() {
String urlAddressForTafs = new StringBuilder()
.append(StaticValues.MAIN_URL_ADDRESS_FOR_CITY_PAIRS_TAFS)
.append(pc.getDepartureAndArrivalTime().get("departureTime"))
.append(StaticValues.END_TIME_STRING)
.append(pc.getDepartureAndArrivalTime().get("arrivalTime"))
.append(StaticValues.STATION_STRING)
.append(pc.getOriginIcao())
.append(",%20")
.append(pc.getDestinationIcao())
.append(StaticValues.MOST_RECENT_FOR_TYPED_STATIONS_TAFS)
.toString();
return urlAddressForTafs;
}
}
And createUrlAddress method:
public String createUrlAddress(TypeOfInformation typeOfInformation, String icao) {
switch (typeOfInformation) {
case METAR:
urlAddress = StaticValues.MAIN_URL_ADDRESS_FOR_METAR + icao;
break;
case TAF:
urlAddress = StaticValues.MAIN_URL_ADDRESS_FOR_TAF + icao + StaticValues.TAF_4_HOURS_BEFORE_NOW;
break;
case CITY_PAIR_METAR:
urlAddress = addressService.createUrlForMetar();
break;
case CITY_PAIR_TAFS:
urlAddress = addressService.createUrlForTaf();
break;
default:
System.out.println("Wrong Type of informations");
}
return urlAddress;
}
Do you think that it is better approach? I cannot reduce code during building a URL String, because there are 3 different parts of code for Tafs and Metars. Could you show me the best way how to test it if my test are bad?
I think you are in the right direction! You are mocking the dependencies of your code and that dependency is exactly the PageControllerForNearestCity!
One observation about the mock, you have to inject it on xmlParser, like this:
#Test
public void forGivenTypeOfInformationAndIcaoReturnUrl() {
// here you created the mock
PageControllerForNearestCity pcSpy = Mockito.spy(pc);
// I'll assume that xmlParser is the subject of your test
// and that you're injecting the mock like code below
xmlParser.setPageController(pcSpy);
// Configure the mock and then you do the assertion
assertThat(...)
}
PS I thought that the method has only one resposibility, just create a
string, am I wrong? Should it be divided into another one like a
"Service"?
Your method is good! It really do one thing and well and that is building an url from TypeOfInformation
My suggestion is that you refactor your code, after you write your test codes and make it pass! You can remove code duplication and make it more readable!
Remeber this:
'Any fool can write code that a computer can understand. Good
programmers write code that humans can understand.'
Martin Fowler
Good coding!
Edit
Some examples of your code with some refactoring
public String createUrlAddress(TypeOfInformation typeOfInformation, String icao) {
String urlAddress;
switch (typeOfInformation) {
case METAR:
urlAddress = StaticValues.MAIN_URL_ADDRESS_FOR_METAR + icao;
break;
case TAF:
urlAddress = StaticValues.MAIN_URL_ADDRESS_FOR_TAF + icao + StaticValues.TAF_4_HOURS_BEFORE_NOW;
break;
case CITY_PAIR_METAR:
// We delegate the build logic to pc because
// all the information needed to build the url
// is in the PageControllerForNearestCity class
urlAddress = pc.urlAddressForCityPairMetar();
break;
case CITY_PAIR_TAFS:
// Same
urlAddress = pc.urlAddressForCityPairTafs();
break;
default:
System.out.println("Wrong Type of informations");
}
return urlAddress;
}
class PageControllerForNearestCity {
public String urlAddressForCityPairMetar() {
return urlBasedOn(StaticValues.MAIN_URL_ADDRESS_FOR_CITY_PAIRSS, ",", StaticValues.MOST_RECENT_FOR_TYPED_STATIONS);
}
public String urlAddressForCityPairTafs() {
return urlBasedOn(StaticValues.MAIN_URL_ADDRESS_FOR_CITY_PAIRS_TAFS, ",%20", StaticValues.MOST_RECENT_FOR_TYPED_STATIONS_TAFS);
}
// This method removes the duplication that I mentioned before
private String urlBasedOn(String mainUrl, String separator, String endString) {
return mainUrl
+ this.getDepartureAndArrivalTime().get("departureTime")
+ StaticValues.END_TIME_STRING
+ this.getDepartureAndArrivalTime().get("arrivalTime")
+ StaticValues.STATION_STRING
+ this.getOriginIcao()
+ separator
+ this.getDestinationIcao()
+ endString;
}
}
Note that after this refactoring, your forGivenTypeOfInformationAndIcaoReturnUrl test method will become much simpler. But you will have to create test for urlAddressForCityPairMetar() and urlAddressForCityPairTafs().
I still new to Junit test. I have a switch-case as code below.
public void manageTrans(ISOMsgZxc isoMsgZxc) {
AbcEntry abcEntry = new AbcEntry();
abcEntry.setEntryMid(isoMsgZxc.getMid());
String mti = isoMsgZxc.getMti() + "." + isoMsgZxc.getProcessingCode().substring(0, 2);
String transType = "";
BigDecimal amt = new BigDecimal("00.000");
switch (mti) {
case "1234.14":
case "0212.02":
transType = "S";
amt = new BigDecimal(isoMsgZxc.getTransactionAmount()).negate();
break;
case "0400.20":
case "0200.22":
transType = "R";
amt = new BigDecimal(isoMsgZxc.getTransactionAmount());
break;
}
abcEntry.setEntryType(transType);
abcEntryRepository.saveAndFlush(abcEntry);
}
Here how I testing it by using #Test
#Test
public void manageTrans() throws Exception {
AbcEntry abcEntry = mock(abcEntry.class);
PowerMockito.whenNew(AbcEntry.class).withNoArguments()
.thenReturn(abcEntry);
ISOMsgZxc isoMsgZxc = new ISOMsgZxc();
isoMsgZxc.setMti("0100");
isoMsgZxc.setMid("0100");
isoMsgZxc.setProcessingCode("000012");
isoMsgZxc.setTransactionAmount("00.000");
txnService.manageTrans(isoMsgZxc);
verify(abcEntry).setEntryMid(isoMsgZxc.getMid());
String asd = "0400.20";
if(asd.equals("0400.20") || (mti.equals("0200.02")))
{
verify(abcEntry).setEntryType("R");
}
verify(abcEntryRepositoryMock).saveAndFlush(abcEntry);
}
So far the testing show pass. But are there any others method to test the switch-case ? What is the best way to test the switch case so all the possible value can be tested? Any help would be greatly appreciated.
Thanks in advance !
It seems like you're trying to test manageTrans, but in a strange fashion due to the structure of your code (mixing business and persistence logic).
You could have a generateEntry(ISOMsgZxc) which creates and returns the AbcEntry:
public AbcEntry generateEntry(ISOMsgZxc isoMsgZxc) {
AbcEntry abcEntry = new AbcEntry();
abcEntry.setEntryMid(isoMsgZxc.getMid());
String mti = isoMsgZxc.getMti() + "." + isoMsgZxc.getProcessingCode().substring(0, 2);
String transType = "";
BigDecimal amt = new BigDecimal("00.000");
switch (mti) {
case "1234.14":
case "0212.02":
transType = "S";
amt = new BigDecimal(isoMsgZxc.getTransactionAmount()).negate();
break;
case "0400.20":
case "0200.22":
transType = "R";
amt = new BigDecimal(isoMsgZxc.getTransactionAmount());
break;
}
abcEntry.setEntryType(transType);
return abcEntry;
}
This will allow you to test generateEntry to verify the entry after:
#Test
public void generateEntry() {
ISOMsgZxc isoMsgZxc = new ISOMsgZxc();
isoMsgZxc.setMti("0100");
isoMsgZxc.setMid("0100");
isoMsgZxc.setProcessingCode("000012");
isoMsgZxc.setTransactionAmount("00.000");
AbcEntry entry = txnService.generateEntry(isoMsgZxc);
//verfiy
verify(abcEntry).setEntryMid(isoMsgZxc.getMid());
Map<String, String> expectedValues = new HashMap<>();
expectedValues.put("0400.20", "R");
expectedValues.put("0200.02", "R");
//...
expectedValues.forEach((input, output) -> verify(input).setEntryType(output));
}
In your production code, simply call:
entryRepo.saveAndFlush(generateEntry())
Easier to maintain (room for validation), easier to test (concerns are separated).
Assuming you want to test the persistence part, you should create another test.
manageTrans would look like this:
public void manageTrans(ISOMsgZxc isoMsgZxc) {
AbcEntry entry = generateEntry();
entryRepo.saveAndFlush(entry);
}
And your test would simply check if the entry exists in the repo after calling manageTrans. Although chances are, saveAndFlush has already been tested, so the manageTrans really wouldn't need testing, as it's implementation consists of already-tested code, and there is no special integration required.
The test for the third test case could look like this.
#Test
public void manageTrans() throws Exception {
ISOMsgZxc isoMsgZxc = new ISOMsgZxc();
isoMsgZxc.setMti("0200");
isoMsgZxc.setMid("0100");
isoMsgZxc.setProcessingCode("220012");
isoMsgZxc.setTransactionAmount("00.000");
//mti should now be = 0200.00, the third case
txnService.manageTrans(isoMsgZxc);
assertThat(abcEntry.getEntryMid(), equalTo(isoMsgZxc.getMid()));
assertThat(abcEntry.getEntryType(), equalTo("R"));
verify(jcbEntryRepositoryMock).saveAndFlush(jcbEntry);
}
To cover the other test cases:
modify the two lines isoMsgZxc.setMti("0200"); and isoMsgZxc.setProcessingCode("220012"); such that they enter the correct case
you will need 5 tests for full coverage
I need to add some or clauses to query. I need to do it in a loop.
StringTokenizer st = new StringTokenizer(symptoms, ",");
while(st.hasMoreTokens()){
qb.whereOr(Properties.Symptom.like("%" + st.nextToken() + "%"));
}
How I can add those or conditions properly, because this above is not working as expected. I want to add or for every symptom.
If you look at the documentation, you'll see that whereOr() takes an unbounded number of conditions. What you want to do is add them all at once in an array:
StringTokenizer st = new StringTokenizer(symptoms, ",");
ArrayList<WhereCondition> whereConditions = new ArrayList<WhereCondition>();
while(st.hasMoreTokens()){
whereConditions.add(Properties.Symptom.like("%" + st.nextToken() + "%"));
}
// Give the ArrayList an already allocated array to place its contents in.
WhereCondition[] conditionsArray = new WhereCondition[whereConditions.size()];
conditionsArray = whereConditions.toArray(conditionsArray);
qb.whereOr(conditionsArray);
It looks like the method call in the documentation takes two non-array WhereConditions and then an ellipsized argument, which accepts an array or an additional comma-separated list of objects. So you might have to do something like this to get it to work properly:
qb.whereOr(conditionsArray[0], conditionsArray[1], Arrays.copyOfRange(conditionsArray, 2, conditionsArray.length));
ADDENDUM: It looks like you're using APIs that don't match the documentation, possibly an older version of greenDAO. I wrote this solution based off the current documentation. I can't guarantee that it will work for you. I recommend updating if possible.
Try this:
StringTokenizer st = new StringTokenizer(symptoms, ",");
WhereCondition where = null;
while(st.hasMoreTokens()){
if (where != null) {
where = qb.or(where, Properties.Symptom.like("%" + st.nextToken() + "%"));
} else {
where = Properties.Symptom.like("%" + st.nextToken() + "%");
}
}
qb.where(where).list();
I had the same problem so I added my own method in an Util class to perform the same behavior when I have one or several WhereCondition in an array.
Here is my gateway method :
public static QueryBuilder whereOr(QueryBuilder queryBuilder, WhereCondition[] whereConditions){
if(whereConditions == null) return queryBuilder.where(null);
else if(whereConditions.length == 1) return queryBuilder.where(whereConditions[0]);
else return queryBuilder.whereOr(whereConditions[0], whereConditions[1], Arrays.copyOfRange(whereConditions, 2, whereConditions.length));
}
Use : Util.whereOr(queryBuilder, whereConditionsArray);
Default : Can't use the Builder Pattern from the QueryBuilder with this approach
(More later) Here, I share you some code which could spare you time when developping DAO methods.
public class QueryBuilderUtil {
public static final String EQ = "=?";
public static final String NOTEQ = "<>?";
public static final String LIKE = " LIKE ?";
public static final String GE = ">=?";
public static final String LE = "<=?";
public static final String GT = ">?";
public static final String LT = "<?";
public static QueryBuilder whereOrOnSamePropertyWithDifferentValues(QueryBuilder queryBuilder, Property property, String operation, String values, String separator) {
return whereOrOnSamePropertyWithDifferentValues(queryBuilder, property, operation, values.split(separator));
}
public static QueryBuilder whereOrOnSamePropertyWithDifferentValues(QueryBuilder queryBuilder, Property property, String operation, String[] values) {
WhereCondition[] whereConditions = new WhereCondition[values.length];
int i = 0;
for (String value : values) {
whereConditions[i++] = new WhereCondition.PropertyCondition(property, operation, value);
}
return whereOr(queryBuilder, whereConditions);
}
public static QueryBuilder whereOr(QueryBuilder queryBuilder, WhereCondition[] whereConditions) {
if (whereConditions == null) return queryBuilder.where(null);
else if (whereConditions.length == 1) return queryBuilder.where(whereConditions[0]);
else return queryBuilder.whereOr(whereConditions[0], whereConditions[1], Arrays.copyOfRange(whereConditions, 2, whereConditions.length));
}
}
With this class, you can perform a whereOr with the same property on multiples "values string" in one line. It was necessary to clean my code :). However you can only do simple operations like variables declared in the class.
Example :
public List<Block> loadAllByModId(String mods_id) {
synchronized (this) {
QueryBuilder<Block> queryBuilder = queryBuilder();
QueryBuilderUtil.whereOrOnSamePropertyWithDifferentValues(queryBuilder, Properties.ModId, QueryBuilderUtil.EQ, mods_id, ";");
query_list = queryBuilder.build();
}
Query<Block> query = query_list.forCurrentThread();
return query.list();
}
Hope it helps
I'm trying to create a validate java class that receives 4 inputs from an object passed as 1 from the requester. The class needs to convert float inputs to string and evaluate each input to meet a certain format and then throw exceptions complete with error message and code when it fails.
What I have is in two methods and would like to know if there is a better way to combine these two classes into one validate method for the main class to call. I don't seem to be able to get around using the pattern/matcher concept to insure the inputs are formatted correctly. Any help you can give would be very much appreciated.
public class Validator {
private static final String MoneyPattern ="^\\d{1,7}(\\.\\d{1,2})$" ;
private static final String PercentagePattern = "^\\d{1,3}\\.\\d{1,2}$";
private static final String CalendarYearPattern = "^20[1-9][0-9]$";
private int errorcode = 0;
private String errormessage = null;
public Validator(MyInput input){
}
private boolean verifyInput(){
String Percentage = ((Float) input.getPercentage().toString();
String Income = ((Float) input.getIncome().toString();
String PublicPlan = ((Float) input.getPublicPlan().toString();
String Year = ((Float) input.getYear();
try {
if (!doesMatch(Income, MoneyPattern)) {
errormessage = errormessage + "income,";
}
if (!doesMatch(PublicPlan, MoneyPattern)) {
errormessage = errormessage + "insurance plan,";
}
if (!doesMatch(Percentage, PercentagePattern)) {
errormessage = errormessage + "Percentage Plan,";
}
if (!doesMatch(Year, CalendarYearPattern)) {
errormessage = errormessage + "Year,";
}
} catch (Exception e){
errorcode = 111;
errormessage = e.getMessage();
}
}
private boolean doesMatch(String s, String pattern) throws Exception{
try {
Pattern p = Pattern.compile(pattern);
Matcher m = p.matcher(s);
if (!s.equals("")){
if(m.find()){
return true;
} else {
return false;
}
}else {
return false;
}
} catch (PatternSyntaxException pse){
errorcode = 111;
errormessage = pse.getMessage();
}
}
}
This code is borked from the word "go". You have a constructor into which you pass a MyInput reference, but there's no code in the ctor and no private data member to receive it. It looks like you expect to use input in your doesMatch() method, but it's a NullPointerException waiting to happen.
Your code doesn't follow the Sun Java coding standards; variable names should be lower case.
Why you wouldn't do that input validation in the ctor, when you actually receive the value, is beyond me. Perhaps you really meant to pass it into that verifyInput() method.
I would worry about correctness and readability before concerning myself with efficiency.
I'd have methods like this:
public boolean isValidMoney(String money) { // put the regex here }
public boolean isValidYear(String year) { // the regex here }
I think I'd prefer a real Money class to a String. There's no abstraction whatsoever.
Here's one bit of honesty:
private static final String CalendarYearPattern = "^20[1-9][0-9]$";
I guess you either don't think this code will still be running in the 22nd century or you won't be here to maintain it.
One way of doing this would be with DynamicBeans.
package com.acme.validator;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.beanutils.PropertyUtils;
public class Validator {
//A simple optimisation of the pattern
private static final Pattern MoneyPattern = Pattern.compile("^\\d{1,7}(\\.\\d{1,2})$");
private static final Pattern PercentagePattern = Pattern.compile("^\\d{1,3}\\.\\d{1,2}$");
private static final Pattern CalendarYearPattern = Pattern.compile("^20[1-9][0-9]$");
public String Validator(MyInput input) {
String errormessage = "";
/*
* Setting these up as Maps.
* Ideally this would be a 'simple bean'
* but that goes beyond the scope of the
* original question
*/
Map<String,Pattern> patternMap = new HashMap<String,Pattern>();
patternMap.put("percentage", PercentagePattern);
patternMap.put("publicPlan", MoneyPattern);
patternMap.put("income", MoneyPattern);
patternMap.put("year", CalendarYearPattern);
Map<String,String> errorMap = new HashMap<String,String>();
errorMap.put("percentage", "Percentage Plan,");
errorMap.put("publicPlan", "insurance plan,");
errorMap.put("income", "income,");
errorMap.put("year", "Year,");
for (String key : patternMap.keySet()) {
try {
String match = ((Float) PropertyUtils.getSimpleProperty(input, key)).toString();
Matcher m = patternMap.get(key).matcher(match);
if ("".equals(match) || !m.find()) {
errormessage = errormessage + errorMap.get(key);
}
} catch (Exception e) {
errormessage = e.getMessage(); //since getMessage() could be null, you need to work out some way of handling this in the response
//don't know the point of the error code so remove this altogether
break; //Assume an exception trumps any validation failure
}
}
return errormessage;
}
}
I've made a few assumptions about the validation rules (for simplicity used 2 maps but you could also use a single map and a bean containing both the Pattern and the Message and even the 'error code' if that is important).
The key 'flaw' in your original setup and what would hamper the solution above, is that you are using 'year' as Float in the input bean.
(new Float(2012)).toString()
The above returns "2012.0". This will always fail your pattern. When you start messing about with the different types of objects potentially in the input bean, you may need to consider ensuring they are String at the time of creating the input bean and not, as is the case here, when they are retrieved.
Good Luck with the rest of your Java experience.