From 395b0a015fe7d4818e6d15ddcec37daef9a38f39 Mon Sep 17 00:00:00 2001 From: DmitriyMX Date: Sun, 3 Jan 2021 00:00:29 +0300 Subject: [PATCH] add tests for JdbcTemplate --- build.gradle | 31 ++- .../ghast/database/DataAccessException.java | 16 ++ .../java/ghast/database/JdbcTemplate.java | 5 +- .../java/ghast/database/JdbcTemplateTest.java | 245 ++++++++++++++++++ 4 files changed, 293 insertions(+), 4 deletions(-) create mode 100644 src/test/java/ghast/database/JdbcTemplateTest.java diff --git a/build.gradle b/build.gradle index aa87b3b..9eb2d6b 100644 --- a/build.gradle +++ b/build.gradle @@ -18,6 +18,8 @@ repositories { } ext { + junitVersion = '5.5.2' + libs = [ bukkit: [lib: 'org.bukkit:bukkit:1.12.2-R0.1-SNAPSHOT', exclude: [ 'com.google.code.gson:gson', @@ -28,7 +30,16 @@ ext { commons_text: 'org.apache.commons:commons-text:1.9', lombok: 'org.projectlombok:lombok:1.18.12', reflection_object: 'ru.dmitriymx:reflection-object:1.0-BETA', - mysql: 'mysql:mysql-connector-java:8.0.22' + mysql: 'mysql:mysql-connector-java:8.0.22', + test: [ + junit5: [ + "org.junit.jupiter:junit-jupiter-api:$junitVersion", + "org.junit.jupiter:junit-jupiter-engine:$junitVersion" + ], + mock: ['org.mockito:mockito-core:1.10.19'], + h2db: 'com.h2database:h2:1.4.200' + ] + ] } @@ -41,6 +52,15 @@ def compileOnly2(library) { } } +def testImplementation2(library) { + dependencies.testImplementation library.lib, { + library.exclude.each { String excludeLibStr -> + String[] excludeLib = excludeLibStr.split(':') + exclude group: excludeLib[0], module: excludeLib[1] + } + } +} + dependencies { compileOnly libs.lombok annotationProcessor libs.lombok @@ -49,4 +69,13 @@ dependencies { compileOnly libs.mysql implementation libs.commons_text implementation libs.reflection_object + + testImplementation libs.test.junit5 + testImplementation libs.test.mock + testImplementation2 libs.bukkit + testImplementation libs.test.h2db +} + +test { + useJUnitPlatform() } \ No newline at end of file diff --git a/src/main/java/ghast/database/DataAccessException.java b/src/main/java/ghast/database/DataAccessException.java index 78c469b..5c686b5 100644 --- a/src/main/java/ghast/database/DataAccessException.java +++ b/src/main/java/ghast/database/DataAccessException.java @@ -1,12 +1,28 @@ package ghast.database; +import lombok.Getter; + +@SuppressWarnings("java:S1165") +@Getter public class DataAccessException extends RuntimeException { + private String sql; + public DataAccessException(String msg) { super(msg); } + public DataAccessException(String msg, String sql) { + this(msg); + this.sql = sql; + } + public DataAccessException(String msg, Throwable cause) { super(msg, cause); } + + public DataAccessException(String msg, String sql, Throwable cause) { + this(msg, cause); + this.sql = sql; + } } diff --git a/src/main/java/ghast/database/JdbcTemplate.java b/src/main/java/ghast/database/JdbcTemplate.java index 0a3ab03..c9e629a 100644 --- a/src/main/java/ghast/database/JdbcTemplate.java +++ b/src/main/java/ghast/database/JdbcTemplate.java @@ -33,7 +33,7 @@ public class JdbcTemplate implements JdbcOperations { statement = connection.createStatement(); statement.execute(sql); } catch (SQLException e) { - XLog.error("Error execute SQL: {0}", e.getMessage(), e); + throw new DataAccessException("Error execute SQL", sql, e); } finally { closeStatement(statement); closeConnection(connection); @@ -52,8 +52,7 @@ public class JdbcTemplate implements JdbcOperations { resultSet = statement.executeQuery(sql); return rse.extractData(resultSet); } catch (SQLException e) { - XLog.error("Error execute SQL: {0}", e.getMessage(), e); - return null; + throw new DataAccessException("Error execute SQL", sql, e); } finally { closeResultSet(resultSet); closeStatement(statement); diff --git a/src/test/java/ghast/database/JdbcTemplateTest.java b/src/test/java/ghast/database/JdbcTemplateTest.java new file mode 100644 index 0000000..c5ff0d2 --- /dev/null +++ b/src/test/java/ghast/database/JdbcTemplateTest.java @@ -0,0 +1,245 @@ +package ghast.database; + +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Lists; +import ghast.GhastTools; +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; +import org.bukkit.plugin.Plugin; +import org.h2.jdbcx.JdbcDataSource; +import org.junit.jupiter.api.*; + +import javax.sql.DataSource; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.text.MessageFormat; +import java.util.List; +import java.util.Map; +import java.util.StringJoiner; +import java.util.logging.Logger; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +class JdbcTemplateTest { + + static final String JDBC_USER = "sa"; + static final String JDBC_PASSWORD = ""; + static final String JDBC_DB_NAME = "in_mem_db"; + static final String JDBC_URL = "jdbc:h2:mem:" + JDBC_DB_NAME + ";DB_CLOSE_DELAY=-1"; + + static final String TABLE_NAME = "TEST_TABLE"; + static final String COLUMN_ID = "ID"; + static final String COLUMN_NAME = "C_NAME"; + static final String COLUMN_VALUE = "C_VALUE"; + + static final Object[][] DATA = new Object[][]{ + { "Player 1", 100 }, { "Player 2", 250 }, + { "Player 3", 0 }, { "Player 4", 780 } + }; + + static DataSource dataSource; + JdbcTemplate jdbcTemplate; + + @BeforeAll + static void beforeAll() { + Logger logger = Logger.getLogger(JdbcTemplateTest.class.getName()); + + Plugin mockPlugin = mock(Plugin.class); + when(mockPlugin.getLogger()).thenReturn(logger); + + GhastTools.setPlugin(mockPlugin); + + JdbcDataSource jdbcDataSource = new JdbcDataSource(); + jdbcDataSource.setUser(JDBC_USER); + jdbcDataSource.setPassword(JDBC_PASSWORD); + jdbcDataSource.setURL(JDBC_URL); + + dataSource = jdbcDataSource; + } + + @BeforeEach + void before() { + jdbcTemplate = new JdbcTemplate(dataSource); + createTable(); + + String sql_head = MessageFormat.format("INSERT INTO {0} ({1}, {2}) VALUES ", + TABLE_NAME, COLUMN_NAME, COLUMN_VALUE); + StringJoiner sql_sj = new StringJoiner(", "); + for (Object[] datum : DATA) { + sql_sj.add(MessageFormat.format("( ''{0}'', {1} )", datum[0], datum[1])); + } + + jdbcTemplate.execute(sql_head + sql_sj.toString()); + } + + @AfterEach + void after() { + dropTable(); + } + + @Test + void testQuery_Single() { + String sql = MessageFormat.format("SELECT {2} FROM {0} WHERE {1} LIKE ''{3}''", + TABLE_NAME, COLUMN_NAME, COLUMN_VALUE, DATA[0][0]); + + Integer value = jdbcTemplate.query(sql, rs -> { + if (rs.next()) { + return rs.getInt(1); + } else { + return null; + } + }); + + assertEquals(DATA[0][1], value); + } + + @Test + void testQuery_List() { + String sql = MessageFormat.format("SELECT {2} FROM {0} WHERE {1} LIKE ''{3}'' OR {1} LIKE ''{4}''", + TABLE_NAME, COLUMN_NAME, COLUMN_VALUE, DATA[0][0], DATA[1][0]); + + List listValues = jdbcTemplate.query(sql, (rs, rowNum) -> rs.getInt(1)); + + assertIterableEquals(Lists.newArrayList(DATA[0][1], DATA[1][1]), listValues); + } + + @Test + void testQueryForObject() { + class Player { + String name; + int value; + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof Player)) return false; + Player player = (Player) o; + return new EqualsBuilder().append(value, player.value).append(name, player.name).isEquals(); + } + + @Override + public int hashCode() { + return new HashCodeBuilder(17, 37).append(name).append(value).toHashCode(); + } + } + + String sql = MessageFormat.format("SELECT {1}, {2} FROM {0} WHERE {1} LIKE ''{3}''", + TABLE_NAME, COLUMN_NAME, COLUMN_VALUE, DATA[0][0]); + + Player actualPlayer = jdbcTemplate.queryForObject(sql, (rs, rowNum) -> { + Player player0 = new Player(); + player0.name = rs.getString(COLUMN_NAME); + player0.value = rs.getInt(COLUMN_VALUE); + + return player0; + }); + + Player expectedPlayer = new Player(); + expectedPlayer.name = (String) DATA[0][0]; + expectedPlayer.value = (int) DATA[0][1]; + + assertEquals(expectedPlayer, actualPlayer); + } + + @Test + void testQueryForMap() { + String sql = MessageFormat.format("SELECT {1}, {2} FROM {0} WHERE {1} LIKE ''{3}''", + TABLE_NAME, COLUMN_NAME, COLUMN_VALUE, DATA[0][0]); + + Map actualMap = jdbcTemplate.queryForMap(sql); + + Map expectedMap = ImmutableMap.of( + COLUMN_NAME, DATA[0][0], + COLUMN_VALUE, DATA[0][1]); + + assertIterableEquals(expectedMap.entrySet(), actualMap.entrySet()); + } + + @Test + void testQueryForList() { + String sql = MessageFormat.format("SELECT {1}, {2} FROM {0}", + TABLE_NAME, COLUMN_NAME, COLUMN_VALUE); + + List> actualMapList = jdbcTemplate.queryForList(sql); + + List> expectedMapList = Stream.of(DATA) + .map(datum -> ImmutableMap.of(COLUMN_NAME, datum[0], COLUMN_VALUE, datum[1])) + .collect(Collectors.toList()); + + assertIterableEquals(expectedMapList, actualMapList); + + } + + @Test + void testUpdate() { + String newName = "Player X"; + String sql = MessageFormat.format("UPDATE {0} SET {1} = ''{3}'' WHERE {1} LIKE ''{2}''", + TABLE_NAME, COLUMN_NAME, DATA[0][0], newName); + + int rows = jdbcTemplate.update(sql); + + assertEquals(1, rows); + + sql = MessageFormat.format("SELECT {2} FROM {0} WHERE {1} LIKE ''{3}''", + TABLE_NAME, COLUMN_NAME, COLUMN_VALUE, newName); + + Integer value = jdbcTemplate.query(sql, rs -> { + if (rs.next()) { + return rs.getInt(1); + } else { + return null; + } + }); + + assertEquals(DATA[0][1], value); + } + + private void createTable() { + jdbcTemplate.execute("CREATE TABLE " + TABLE_NAME + " (" + + COLUMN_ID + " bigint auto_increment," + + COLUMN_NAME + " varchar(16)," + + COLUMN_VALUE + " integer)"); + } + + private void dropTable() { + jdbcTemplate.execute("DROP TABLE IF EXISTS " + TABLE_NAME); + } + + @Nested + class JdbcTemplateTest_ExecuteTestCase { + + @BeforeEach + void before() { + jdbcTemplate = new JdbcTemplate(dataSource); + dropTable(); + } + + @AfterEach + void after() { + dropTable(); + } + + @Test + void test() throws ClassNotFoundException, IllegalAccessException, InstantiationException, SQLException { + createTable(); + + //region Check result + Class.forName("org.h2.Driver").newInstance(); + Connection connection = DriverManager.getConnection(JDBC_URL, JDBC_USER, JDBC_PASSWORD); + ResultSet resultSet = connection.getMetaData().getTables(JDBC_DB_NAME.toUpperCase(), "PUBLIC", + TABLE_NAME.toUpperCase(), new String[]{"TABLE"}); + + assertTrue(resultSet.next()); + + resultSet.close(); + connection.close(); + //endregion + } + } +} \ No newline at end of file