Unit Testing MyBatis Handler - java

I have used mybatis, and as part of this I have needed to write a custom handler object to convert from database TIMESTAMP to Java XMLGregorianCalendar object.
After some testing, the conversion seems to work except it's not returning times correctly.
In order to ensure I get this running correctly and that I have tests to prove what can be converted I was trying to write a junit test to cover the conversion.
In the test, I have the following:
XMLGregorianCalendarHandler handler = new XMLGregorianCalendarHandler();
and I am trying to test that the getResults method using:
assertEquals(expectedDate , handler.getResult(rs, "timestamp"));
However, I'm not sure how to actually "create" a dummy ResultSet object to pass in as the 'rs' object so I can process the test.
Can anyone point me in the right direction please? am I even on the right track as to how I should be testing an object handler for mybatis?
The actual test code is:
#Test
public void testConvertTimestamp() throws SQLException, DatatypeConfigurationException{
String dbTimestamp = "24-APR-14 15.47.44.000000000";
XMLGregorianCalendar expectedDate = null;
GregorianCalendar c = new GregorianCalendar();
c.set(GregorianCalendar.DAY_OF_MONTH, 24);
c.set(GregorianCalendar.MONTH, GregorianCalendar.APRIL);
c.set(GregorianCalendar.YEAR, 2014);
c.set(GregorianCalendar.HOUR_OF_DAY, 15);
c.set(GregorianCalendar.MINUTE, 47);
c.set(GregorianCalendar.SECOND, 44);
c.set(GregorianCalendar.MILLISECOND, 000000000);
expectedDate = DatatypeFactory.newInstance().newXMLGregorianCalendar(c);
ResultSet rs;
assertEquals(expectedDate , handler.getResult(rs, RS_COLUMN_NAME));
}

Use any mock library to create resultset mock. In Mockito this can look like:
ResultSet rs = Mockito.mock(ResultSet.class);
Mockito.when(resultSetMock.next()).thenReturn(true).thenReturn(false);
Mockito.when(resultSetMock.getString(RS_COLUMN_NAME)).thenReturn(dbTimestamp);

Related

How can I run a function multiple times with multiple data sets or values?

I'm kinda new to programming and got this as an assignment at work. I need to run a method that sends a message (FIX format) multiple times with multiple data sets. Here's how I build with its respective data and send the message:
private void testCaseAttempt(String testCaseName) throws Exception {
StringBuilder errorBuilder = new StringBuilder();
// Read test case arguments
new Arguments(testCaseName);
QuoteRequestBuilder builder = app.builders().quoteRequest();
BigDecimal b1;
b1 = new BigDecimal(10000);
//Date transactTime;
//transactTime = new Date(0);
//expireTime 10 minutes from now
Calendar now = Calendar.getInstance();
now.add(Calendar.MINUTE, 10);
Date expireTime = now.getTime();
//BUILD THE MESSAGE
builder
.setField(131, "5EB26EAAC074000D0000")
.symbol("DANBNK")
.securityID("SE0011116474")
.currency("SEK")
.securityIDSource("4")
.setField(54, "2")
.expireTime(expireTime)
.orderQty(b1)
.setField(64, "20200508")
.setField(1629, "10")
.setField(1916, "0")
.setField(60, "20200526-15:48:53.006")
.setField(761, "1")
.partyID("13585922", PartyIDSource.PROPRIETARY_CUSTOM_CODE, 11, null)
.partyID("1270", PartyIDSource.PROPRIETARY_CUSTOM_CODE, 13, null)
.partyID("SEB", PartyIDSource.PROPRIETARY_CUSTOM_CODE, 1, null)
.partyID("1786343", PartyIDSource.PROPRIETARY_CUSTOM_CODE, 117, null);
Message quoteRequestMessage = builder.getMessage();
//SEND THE MESSAGE
app.sendMessage(quoteRequestMessage, app.getSession(session));
long timeout = Properties.getLong(0L, "waitForMessage", "FIX");
Message responseMessage;
}
I build the FIX message with the "setfield" instructions then I just send it. This works just fine except I need to do it 20-30 times (so 20-30 messages) and I need to slightly change the values or parameteres each time.
I have an idea how to do this with cucumber using a feature file with an "Examples" table with my desired data so it calls this method but that feels like overkill at the moment. I was thinking of using an excel file with a table so I can comfortably change the values in each row and just feed it to this function somehow.
By the way, I didn't copy all the code in the function, I just copied the lines in which the msg is built and sent.
Any idea how I can do this? Your replies are much appreciated!
Thanks in advance.
Create your field map and just iterate that field map as below
Map<Integer,String> fieldMap = new HashMap<>();
fieldMap.put(131,"5EB26EAAC074000D0000");
fieldMap.put(54,"2");
fieldMap.put(64,"20200508");
fieldMap.put(1629,"10");
fieldMap.forEach((k,v)->{
builder.setField(k,v)
});

Apache Calcite - registering UDFs for use in RelBuilder

The OOTB sample udf junits (UdfTest.java) make use of a dummy jdbc schema and do not show the usage of RelBuilder api.
I am working on registering a simple UDF that returns the length of the input string. I have created the SqlFunction and registered the same in the SqlStdOperatorTable -
SqlFunction length = new SqlFunction("STRLEN",
SqlKind.OTHER_FUNCTION,
ReturnTypes.INTEGER,
null,
OperandTypes.STRING,
SqlFunctionCategory.USER_DEFINED_FUNCTION);
SqlStdOperatorTable sqlStdOperatorTable = SqlStdOperatorTable.instance();
sqlStdOperatorTable.register(length);
And used this for creating the FrameworkConfig -
FrameworkConfig frameworkConfig = Frameworks.newConfigBuilder()
.parserConfig(SqlParser.Config.DEFAULT)
.defaultSchema(connection.getRootSchema().getSubSchema("SYSTEM"))
.programs(Programs.sequence(Programs.ofRules(Programs.RULE_SET), Programs.CALC_PROGRAM))
.operatorTable(sqlStdOperatorTable)
.build();
Now I can use the predefined sql functions like substr, defined in class SqlStringLengthFunction, with following piece-
RelNode udfRelNode = builder
.scan("EMP")
.project(builder.call(new SqlStringLengthFunction(),builder.literal("SampleString"), builder.literal(3))
.build();
PreparedStatement statement = RelRunners.run(udfRelNode);
ResultSet resultSet = statement.executeQuery();
but when I try with the above function 'length' in builder.call, it throws exception -
java.lang.RuntimeException: cannot translate call STRLEN($t3)
The builder fetches the implementation of these functions from a private map in RexImpTable class.
No public/protected apis are exposed in this class for adding values to this map.
Can you please guide how to register any UDF with Calcite and use the same with RelBuilder ?
A comment on calcite jira for my question here answers the question with one approach-
This is because SqlStdOperatorTable.instance() did some initialization work for the registered functions. So an invoke of #register after it would not work as expected. The correct way is to use ListSqlOperatorTable and chained it with the StdSqlOperatorTable with ChainedSqlOperatorTable, the presudo code may like this:
ListSqlOperatorTable listOpTable = new ListSqlOperatorTable();
listOpTable.add(my_udf);
ChainedSqlOperatorTable chainedOpTable = ChainedSqlOperatorTable.of(listOpTable, SqlStdOperatorTable.instance());
// then use this chainedOpTable
// If you want to use a special dialect operators, you can code like this
SqlOperatorTable optable = SqlLibraryOperatorTableFactory.INSTANCE
.getOperatorTable(SqlLibrary.STANDARD, SqlLibrary.POSTGRESQL);
I resolved my issue with following approach -
// methods containing the udf logic
public static class MyUdf1 {
public Integer eval(String a) {
return a.length();
}
}
#Test
public void test1() throws SQLException, ClassNotFoundException {
CalciteConnection connection = MyTests.getCalciteConnection();
final String functionName = "STR_LEN";
final ScalarFunction udfLengthFunction = ScalarFunctionImpl.create(Types.lookupMethod(MyUdf1.class, "eval", String.class));
connection.getRootSchema().getSubSchema("SYSTEM").add(functionName, udfLengthFunction);
FrameworkConfig frameworkConfig = Frameworks.newConfigBuilder()
.parserConfig(SqlParser.Config.DEFAULT)
.defaultSchema(connection.getRootSchema().getSubSchema("SYSTEM"))
.programs(Programs.sequence(Programs.ofRules(Programs.RULE_SET), Programs.CALC_PROGRAM))
.build();
SqlIdentifier udfLengthIdentifier = new SqlIdentifier(Collections.singletonList(functionName), null, SqlParserPos.ZERO, null);
final SqlOperator strLenOperator = new SqlUserDefinedFunction(udfLengthIdentifier, ReturnTypes.INTEGER, null, OperandTypes.STRING, null, udfLengthFunction);
final RelBuilder builder = RelBuilder.create(frameworkConfig);
RelNode udfRelNode = builder
.scan("EMP")
.project(builder.call(strLenOperator, builder.literal("SampleString")))
.build();
ResultSet set = RelRunners.run(udfRelNode).executeQuery();
set.next();
System.out.println(set.getString(1));
}

Mockito Unit tests - Timestamps are different

Having some issues with a Mockito test.
I currently get this error:
Argument(s) are different! Wanted:
repository.save(
uk.co.withersoft.docservice.repositories.hibernate.MetaDataEntity#3e437e6c
);
-> at uk.co.withersoft.docservice.datastore.impl.MetaDataStoreImplTest.storeClaimMetadata(MetaDataStoreImplTest.java:55)
Actual invocation has different arguments:
repository.save(
uk.co.withersoft.docservice.repositories.hibernate.MetaDataEntity#3e361ee2
);
I'm pretty sure its because the times within MetaDataEntity are different
//This is what I should be getting
id = null
metaData = "{"caseReference":"CN00000001","claimReference":"LN00000001","rpsDocumentType":"REJ","documentTitle":"Claims LN00000001 (Claimant: Mr LOCAL HOST) REJ-Rejection letter"}"
batchId = 0
state = "Saved MetaData to DB"
lastUpdatedDate = {Timestamp#1517} "2018-07-25 18:39:21.993"
createdDate = {Timestamp#1518} "2018-07-25 18:39:21.993"
// This is actually what I get.
id = null
metaData = "{"caseReference":"CN00000001","claimReference":"LN00000001","rpsDocumentType":"REJ","documentTitle":"Claims LN00000001 (Claimant: Mr LOCAL HOST) REJ-Rejection letter"}"
batchId = 0
state = "Saved MetaData to DB"
lastUpdatedDate = {Timestamp#1530} "2018-07-25 18:39:49.274"
createdDate = {Timestamp#1531} "2018-07-25 18:39:52.716"
Here is my test case:
#Test
public void storeClaimMetadata () throws JsonProcessingException {
ClaimMetaData metaData = constructMetaData();
MetaDataEntity mockResponseMetaDataEntity = new MetaDataEntity();
mockResponseMetaDataEntity.setId(1);
when(repository.save(any(MetaDataEntity.class))).thenReturn(mockResponseMetaDataEntity);
Integer result = testSubject.storeClaimMetadata(metaData);
assertEquals(Integer.valueOf(1), result);
final ObjectMapper mapper = new ObjectMapper();
String jsonMetaData = mapper.writeValueAsString(metaData);
MetaDataEntity expectedMetaDataEntity = new MetaDataEntity(null,
jsonMetaData,
0,
"Saved MetaData to DB",
new Timestamp(System.currentTimeMillis()),
new Timestamp(System.currentTimeMillis()));
Mockito.verify(repository, times(1)).save(expectedMetaDataEntity);
}
//Creates a ClaimRequest
private ClaimMetaData constructMetaData() {
final ClaimMetaData metaData = new ClaimMetaData("CN00000001",
"LN00000001",
"REJ",
"Claims LN00000001 (Claimant: Mr LOCAL HOST) REJ-Rejection letter");
return metaData;
}
Any help would be much appreciated. This has been driving me crazy!!
This is exactly why people use dependency injection, so they can specify test collaborators that give back predictable results. Replace the hardcoded new Timestamp(System.currentTimeMillis) stuff with calls to Timestamp.from(Instant.now(clock)).
java.time.Clock is an interface that you can use to get your timestamp values. The real implementation can be injected into the code being tested, using one of the factory methods that returns a system clock, like this (using Spring Java configuration):
#Bean
public Clock clock() {
return Clock.systemDefaultZone();
}
and for the test code you can have an implementation where you specify the time you want the clock to return:
#Before
public void setUp() {
clock = Clock.fixed(date.toInstant(), ZoneId.of("America/NewYork"));
systemUnderTest.setClock(clock);
}
This is "works as designed".
You are invoking a service that computes timestamps. Like, now.
Then you have a test case that has some setup going on, and fetches time stamps, too. Now.
Guess what: albeit these two "nows above are close to each other, there is still a bit of delay between them.
You are checking for equality, can only work when the time stamps are identical! But they aren't, because they are created one after the other, with very well noticeable delays in between!
Meaning: you need to look how you could control which timestamps are created within your application, like saying "the timestamps should be t1 and t2". So that your test can then check "I found t1 and t2".
Alternatively, you simply change your verification step: instead of trying to have "equal" objects (that can't be equal because different time stamps!), you could compare those parts that should be equal, and for the time stamps, you could check that they are "close enough".
In Code , instead of using new Timestamp(System.currentTimeMillis()) , you can use
new Timestamp(DateTimeUtils.currentTimeMillis()). Here DateTimeUtils is from jodatime.
In test cases, can use below.
private SimpleDateFormat DATE_FORMATTER = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss:SSS");
#Before
public void before() throws Exception {
// define a fixed date-time
Date fixedDateTime = DATE_FORMATTER.parse("01/07/2016 16:45:00:000");
DateTimeUtils.setCurrentMillisFixed(fixedDateTime.getTime());
}
#After
public void after() throws Exception {
// Make sure to cleanup afterwards
DateTimeUtils.setCurrentMillisSystem();
}````

DBunit - Unable to typecast <1997/02/14> to TimeStamp

I'm doing an integration testing with DBUnit (2.49) + Hibernate (4.1.3) following this tutorial.
Production database : Oracle 10
Test database : Hsqldb 2.3.3
Context
My data contains the current format of date : yyyy/MM/dd. However,according to DBUnit faq, DBUnit only supports this format yyyy-mm-dd hh:mm:ss.fffffffff, so I had to create a new format for TimeStamp.
How I tried to fix it
I created a CustomTimeStampDataType based on this tutorial. I changed this part:
String formats[] = {"yyyy-MM-dd HH:mm", "yyyy-MM-dd HH:mm a", "yyyy-MM-dd HH:mm:ss.fffffffff"};
into this one:
String formats[] = {"yyyy/MM/dd"};
I created a CustomeDataTypeFactory following the same tutorial. I only make it extend Oracle10DataTypeFactory rather than DefaultDatatTypeFactory.
In HibernateDBUnitTestCase, I override setDatabaseConfig() with the following:
#Override
protected void setUpDatabaseConfig(DatabaseConfig config){
config.setProperty(DatabaseConfig.PROPERTY_DATATYPE_FACTORY, new CustomDataTypeFactory());
}
But I got new errors
I ran a unit test and got this error.
org.dbunit.dataset.datatype.TypeCastException: Unable to typecast value <1997/02/14> of type <java.lang.String> to TIMESTAMP
at org.dbunit.dataset.datatype.TimestampDataType.typeCast(TimestampDataType.java:120)
at org.dbunit.dataset.datatype.TimestampDataType.setSqlValue(TimestampDataType.java:176)
at org.dbunit.database.statement.SimplePreparedStatement.addValue(SimplePreparedStatement.java:73)
at org.dbunit.operation.RefreshOperation$RowOperation.execute(RefreshOperation.java:189)
at org.dbunit.operation.RefreshOperation.execute(RefreshOperation.java:113)
at org.dbunit.AbstractDatabaseTester.executeOperation(AbstractDatabaseTester.java:190)
at org.dbunit.AbstractDatabaseTester.onSetup(AbstractDatabaseTester.java:103)
at org.dbunit.DatabaseTestCase.setUp(DatabaseTestCase.java:156)
at test.HibernateDbUnitTestCase.setUp(HibernateDbUnitTestCase.java:85)
at test.PlayerTest.setUp(PlayerTest.java:117)
Caused by: java.lang.IllegalArgumentException: Timestamp format must be yyyy-mm-dd hh:mm:ss[.fffffffff]
at java.sql.Timestamp.valueOf(Unknown Source)
at org.dbunit.dataset.datatype.TimestampDataType.typeCast(TimestampDataType.java:116)
... 20 more
That was weird, it seemed like my CustomTimeStamp was not called, so I changed the date in the dataset using the default format : 1997-02-14 00:00:00.0, and ran the unit test again. Then I got:
org.dbunit.dataset.datatype.TypeCastException: Unable to typecast value <1997-02-14 00:00:00.0> of type <java.lang.String> to TIMESTAMP
at test.CustomTimestampDataType.typeCast(CustomTimestampDataType.java:69)
at test.CustomTimestampDataType.setSqlValue(CustomTimestampDataType.java:84)
at org.dbunit.database.statement.SimplePreparedStatement.addValue(SimplePreparedStatement.java:73)
at org.dbunit.operation.RefreshOperation$RowOperation.execute(RefreshOperation.java:189)
at org.dbunit.operation.RefreshOperation.execute(RefreshOperation.java:113)
at org.dbunit.AbstractDatabaseTester.executeOperation(AbstractDatabaseTester.java:190)
at org.dbunit.AbstractDatabaseTester.onSetup(AbstractDatabaseTester.java:103)
at org.dbunit.DatabaseTestCase.setUp(DatabaseTestCase.java:156)
at test.HibernateDbUnitTestCase.setUp(HibernateDbUnitTestCase.java:85)
at test.PlayerTest.setUp(PlayerTest.java:117)
That means CustomTimeStamp was actually called. Seems like, the problem stemed from DatabaseTestCase.setUp which somehow called the wrong TimeStampDataType.
How could I fix this issue?
My first option was to replace every yyyy/MM/dd into yyyy-mm-dd in the dataset using regular expressions. This worked fine, until I had to test a method that selected a date based on a request (so the format is yyyy-mm-dd) and compared it to the current date. ( so the format is yyyy / mm / dd). Hsqldb can't compare two dates with different format.
My second option was to decompile dbunit.jar, rewrite TimeStampDataType based on the tutorial. I'm unfamiliar with bytecode writing so before entering uncharted waters, I wanted to know if you had another solution.
Thank you in advance
Fixed it!
So I ended up using my second option.
This is the detailed path for those who need it.
Download dbUnit.2.2.source.jar
Unzip the jar
Go to Eclipse, File > New > Java Project
Uncheck "Use default location"
In Location : specify the path to the new folder created from the jar
Click on Finish
Modify the TimestampDataType.java (if needed)
Instead of ts = java.sql.Timestamp.valueOf(stringValue); use the code below
String formats[] =
{"dd/MM/yyyy HH:mm:ss.SS"}; //and more depending on your need
Timestamp ts = null;
for (int i = 0; i < formats.length; i++)
{
SimpleDateFormat sdf = new SimpleDateFormat(formats[i]);
try {
java.util.Date date = sdf.parse(stringValue);
ts = new Timestamp(date.getTime());
return ts;
}
catch( ParseException e) {
}
Modify the DateDataType.java (if needed)
Instead of return java.sql.Date.valueOf(stringValue); , use the code below
String formats[] =
{"dd/MM/yyyy"}; //and more depending on your need
for (int i = 0; i < formats.length; i++)
{
SimpleDateFormat sdf = new SimpleDateFormat(formats[i]);
try {
java.util.Date date = sdf.parse(stringValue);
java.sql.Date datesql = new java.sql.Date(date.getTime());
return datesql;
}
catch( ParseException e) {
}
}
Right-click on your project, then Export
Select JAR file, then Next
Fill the export destination then Finish.
You just have to add this new jar to the library to make it work.

Java method halts when retrieving java.sql.Date object from mysql database, why?

My java class (servlet) has been running fine, but recently, I noticed a problem.
In my database, I have a Date column called 'Full_Expiration_Date'. If one was entered it will store it like '2010-11-17'. If one wasn't entered, is stores it like '0000-00-00'.
In my class, I call the records from the db, and look at the 'Full_Expiration_Date' field to see if it has a date or not. If it does, I execute a few lines of code that check to see if before today's date, and if the date is NOT today's date - if both those conditions are met, the coupon has expired.
So my code works just fine when there IS an expiration date specified, but fails when '0000-00-00' is in the field.
I'm generating an array of information for each coupon in the db table. Once the checking is complete and the array is filled, its sent as a request attribute.
Here's a snippet of my code for this process -
rs = stmt.executeQuery("select Full_Expiration_Date from coupons");
//will hold value from "Full_Expiration_Date" field in db
java.sql.Date expirationDate = null;
//today's date - used for comparison
java.util.Date today = new java.util.Date();
while(rs.next()) {
//get expiration date from db for this record
expirationDate = rs.getDate(12);
if(expirationDate == null) { //should be if the field is 0000-00-00, right?
//don't do any checking against
//expiration date, this record
//doesn't have one
couponList[counter][11] = "";
} else { //10
if(expirationDate.before(today) & !today.equals(expirationDate)) {
couponList[counter][11] = "expired";
} else { //11
couponList[counter][11] = "";
}//if
}//if
counter++;
}//while
Can someone pinpoint what I'm doing wrong here? I'm certain, after testing, that it has to do with the field being 0000-00-00.
0000-00-00 is an invalid date that can't be parsed by Java. You need to allow nulls in that field so that MySQL will stop inserting 0s and that way your if(expirationDate == null) check will actually work.
This is stupid behavior IMO and you should "fix it" by turning on Strict mode. http://dev.mysql.com/doc/refman/5.1/en/server-sql-mode.html#sqlmode_no_zero_date
You can work around this using MySQL's "zeroDateTimeBehavior" configuration property. In your connection properties set:
"zeroDateTimeBehavior" to "convertToNull"
Anytime a 0000-00-00 date is retrieved, it will return null instead of throwing an exception.
Reference: http://dev.mysql.com/doc/refman/5.0/en/connector-j-reference-configuration-properties.html

Categories

Resources