How to test method which uses other method - Mockito, java, junit - java

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().

Related

Unable to write Mockito test case for my void method

I need to test this code with Mockito (JUnit):
public class Calculation {
public void logTimeTaken(String label, long estimatedTime, int size, boolean isDebug) {
String out = label + " took " + TimeUnit.MILLISECONDS.convert(estimatedTime, TimeUnit.NANOSECONDS) + " milliseconds for " + size + " events!";
if (isDebug) {
System.out.println(out);
} else {
System.out.println(out);
}
}
}
I search so many examples google but still not getting any idea.
You can configure System with an instance of PrintStream which you can then assert against after invoking Calculation.logTimeTaken.
Here's an example:
#Test
public void canLogTimeTaken() {
ByteArrayOutputStream bout = new ByteArrayOutputStream();
PrintStream out = new PrintStream(bout);
System.setOut(out);
Calculation sut = new Calculation();
sut.logTimeTaken("label", 20 , 2, false);
assertEquals("if isDebug is false label took 0 milliseconds for 2 events!\n", bout.toString());
}
Note: there is no need for Mockito here, this is just vanilla JUnit, no mocking.
But, it might be a better design to refactor logTimeTaken into two distinct aspects:
Deriving the log message
Logging that message
For example:
public String createTimeTakenMessage(String label, long estimatedTime, int size, boolean isDebug) {
return label + " took " + TimeUnit.MILLISECONDS.convert(estimatedTime, TimeUnit.NANOSECONDS) + " milliseconds for " + size + " events!";
}
public void logTimeTaken(String message) {
System.out.println(message);
}
Then testing createTimeTakenMessage is trivial and you might even choose not to test logTimeTaken at all since all it does is invoke a System method. Or, perhaps you would hide the 'log action' behind an interface with an implementation using System.out now and perhaps, later, other implementations using a formal logging framework such as Logback.

JUnit Test with switch-case

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

how to minimize this switch case?

I am getting different provider through service call.
On that base my tab section will differ; and I want to minimize this code:
if(selectedProvider.equalsIgnoreCase("youtube")){
switch (tabName.toLowerCase()) {
case "songs":
sectionTab = "video";
break;
case "artists":
sectionTab="";
break;
case "albums":
sectionTab="channel";
break;
case "playlists":
sectionTab="playlist";
break;
}}
else if(selectedProvider.equalsIgnoreCase("soundcloud")){
switch (tabName.toLowerCase()) {
case "songs":
sectionTab = "track";
break;
case "artists":
sectionTab="artist";
break;
case "albums":
sectionTab="";
break;
case "playlists":
sectionTab="playlist";
break;
}}
else {
switch (tabName.toLowerCase()) {
case "songs":
sectionTab = "track";
break;
case "artists":
sectionTab = "artist";
break;
case "albums":
sectionTab = "album";
break;
case "playlists":
sectionTab = "playlist";
break;
}
}
One possible solution here: use a map that contains a map.
Like:
Map<String, String> soundCloudMappings = new HashMap<>();
soundCloudMappings.put("songs", "track");
...
Map<String, Map<String, String> providerMappings = ...
providerMappings.put("soundcloud", soundCloudMappings);
And then you can check if provider.toLowerCase() exists in your outer map; and then ask the inner map for the correct sectionTab entry.
But of course, this is a pretty "low level" solution. Depending on your context, you might rather look into turning these raw strings into Enums constants; and add nice mapping methods to that Enum. In other words: consider balancing flexibility (doing everything with strings) with increased compile-time safety.
I prefer to build structures to define the logic rather than coding it in situations like this.
// What tabs we have.
enum Tabs {
songs,
artists,
albums,
playlists;
// Build a lookup.
static Map<String, Tabs> lookup = Arrays.stream(Tabs.values()).collect(Collectors.toMap(e -> e.name(), e -> e));
static Tabs lookup(String s) {
return lookup.get(s);
}
}
// The providers.
enum Providers {
youtube("video","","channel","playlist"),
soundcloud("track","artist","","playlist"),
others("track","artist","album","playlist");
// Build a String lookup.
static Map<String, Providers> lookup = Arrays.stream(Providers.values()).collect(Collectors.toMap(e -> e.name(), e -> e));
Map<Tabs,String> tabs = new HashMap<>();
Providers(String track, String artist, String album, String playlists) {
tabs.put(Tabs.songs, track);
tabs.put(Tabs.artists, artist);
tabs.put(Tabs.albums, album);
tabs.put(Tabs.playlists, playlists);
}
static Providers lookup(String s) {
Providers p = lookup.get(s);
// Default to others.
return p == null ? others : p;
}
public static String getSectionTabName(String provider, String tabName) {
// Lookup the provider.
Providers p = lookup(provider);
Tabs t = Tabs.lookup(tabName);
return p.tabs.get(t);
}
}
public void test() {
String provider = "youtube";
String tabName = "albums";
String section = Providers.getSectionTabName(provider, tabName);
System.out.println(provider+"!"+tabName+" = "+section);
}
Benefits to doing it this way:
Adding new providers just requires adding new enums.
Adding new tabs is a little less trivial (add a new parameter to the Providers constructor) but still does not change the code significantly.
You can use a couple of Maps like this to do the translation
static final Map<String, String> SECTION_TAB2 = new LinkedHashMap<>();
static final Map<String, String> SECTION_TAB1 = new LinkedHashMap<>();
static {
// are there special two word outcomes
SECTION_TAB2.put("youtube songs", "artists");
SECTION_TAB2.put("youtube artists", "");
SECTION_TAB2.put("youtube albums", "channel");
SECTION_TAB2.put("soundcloud albums", "");
// if not, what are the default tab name outcomes.
SECTION_TAB1.put("songs", "track");
SECTION_TAB1.put("artists", "artist");
SECTION_TAB1.put("albums", "album");
SECTION_TAB1.put("playlists", "playlist");
}
public static String sectionTab(String selectedProvider, String tabName) {
return SECTION_TAB2.getOrDefault((selectedProvider + " " + tabName).toLowerCase(),
SECTION_TAB1.get(tabName.toLowerCase()));
}
i am unable to comment so i am commenting as answer for
public static void main(String[] args) throws IOException
{
Table<String,String,String> table= HashBasedTable.create();
table.put("youtube","songs","video");
table.put("youtube","artists","");
table.put("youtube","albums","channel");
table.put("youtube","playlists","playlist");
table.put("soundcloud","songs","track");
table.put("soundcloud","artists","artist");
table.put("soundcloud","albums","");
table.put("soundcloud","playlists","playlist");
table.put("default","songs","track");
table.put("default","artists","artist");
table.put("default","albums","albums");
table.put("default","playlists","playlist");
String sectionTab = table.get("soundcloud","artists"); // u will get artist
}
we can use guava table to avoid map of map and easy to maintain and redable
https://www.tutorialspoint.com/guava/guava_table.htm

Jersey - String not mapped to Json while using ObjectMappers function writeValueAsString succeeds

I have the following function :
protected final ResponseBuilderImpl RESPONSE_BUILDER_IMPL = new ResponseBuilderImpl(); // This is taken from jersey-core-1.11.jar
protected Response buildAcceptedResponse(Object entity) {
return buildResponse(entity, Response.Status.ACCEPTED);
}
protected Response buildResponse(Object entity, Response.Status status) {
return RESPONSE_BUILDER_IMPL.status(status).entity(entity).build();
}
I've tested several cases and noticed that only String is not working, and can't figure out why.
I'm using postman, sending some GET requests
switch (whatToTest){
case 1: //working
log.info("testing case of Interger!! ");
int x = 11;
return buildAcceptedResponse(x);
case 2: // getting as a response Unexpected 'e'
String t = "testing string";
log.info("testing case of String !!");
return buildAcceptedResponse(t);
case 3: //working
log.info("testing case of Boolean !! ");
boolean y = true;
return buildAcceptedResponse(y);
case 4: //not working
log.info("testing case of Object !! ");
Object yy = "test object";
return buildAcceptedResponse(yy);
case 5: //working
log.info("testing case of Object from type int !! ");
Object yyy = 1565746;
return buildAcceptedResponse(yyy);
case 6: //working
log.info("testing case of class with strings and ints!! ");
TestClass e = new TestClass(4,new Integer(7),"test1", false);
return buildAcceptedResponse(e);
/// Adding - when using this case - it works fine
case 7:
log.info("mapping to object");
ObjectMapper om = new ObjectMapper();
Object res = null;
String te = "test test";
try {
res = om.writeValueAsString(te);
} catch (IOException ex) {
ex.printStackTrace();
}
return buildAcceptedResponse(res);
I thought that I might have a problem with primitive types, but int and boolean are returning as they should. Also, a class containing a String inside - is also good.Can someone advise why only Strings are not working ?
Adding -
My function uses :
#GET
#Produces(MediaType.APPLICATION_JSON)
#Path("test/test")
public Response getTest(String someParam);
// Response is sr311-api\0.11\jsr311-api-0.11-sources.jar

GreenDao whereOr and conditions adding in a loop

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

Categories

Resources