Junit test: NoSuchElementException, Mock getConnection

79 Views Asked by At

I am trying to write a JUnit test, but receiving java.util.NoSuchElementException. With limited knowledge of Mockito and JUnit, I understand DriverManager.getConnection needs to be mocked/stubbed. How can I do that?

The aim is to run the JUnit test without access to the actual database.

public class DatabricksQueryExecutorTests {
    @Test
    public void testReadUnityTableDataAsJson() throws SQLException {
        // Create a mock DatabricksConfig object
        DatabricksConfig databricksConfig = new DatabricksConfig();
        databricksConfig.setHost("example.com");
        databricksConfig.setHttpPath("/api/2.0");
        databricksConfig.setAccessToken("your-access-token");
        databricksConfig.setUsername("your-username");

        // Create a mock SQL query
        String sqlQuery = "SELECT * FROM table";

        // Create a mock ResultSet with sample data
        ResultSet resultSet = Mockito.mock(ResultSet.class);
        Mockito.when(resultSet.next()).thenReturn(true, false);
        Mockito.when(resultSet.getMetaData()).thenReturn(Mockito.mock(ResultSetMetaData.class));
        Mockito.when(resultSet.getMetaData().getColumnCount()).thenReturn(2);
        Mockito.when(resultSet.getMetaData().getColumnName(1)).thenReturn("column1");
        Mockito.when(resultSet.getMetaData().getColumnName(2)).thenReturn("column2");
        Mockito.when(resultSet.getString(1)).thenReturn("value1");
        Mockito.when(resultSet.getString(2)).thenReturn("value2");

        // Create a mock Connection and Statement
        Connection connection = Mockito.mock(Connection.class);
        Statement statement = Mockito.mock(Statement.class);
        Mockito.when(connection.createStatement()).thenReturn(statement);
        Mockito.when(connection.createStatement().executeQuery(sqlQuery)).thenReturn(resultSet);

        // Create an instance of DatabricksQueryExecutor
        DatabricksQueryExecutor queryExecutor = new DatabricksQueryExecutor();

        // Call the method under test
        JsonArray jsonArray = queryExecutor.readUnityTableDataAsJson(sqlQuery, databricksConfig);

        // Verify the result
        Assertions.assertEquals(1, jsonArray.size());
        JsonObject jsonObject = jsonArray.get(0).getAsJsonObject();
        Assertions.assertEquals("value1", jsonObject.get("column1").getAsString());
        Assertions.assertEquals("value2", jsonObject.get("column2").getAsString());
    }
}

Error:

java.util.NoSuchElementException
    at java.base/java.util.StringTokenizer.nextToken(StringTokenizer.java:347)
    at com.databricks.client.jdbc.common.BaseConnectionFactory.acceptsURL(Unknown Source)
    at com.databricks.client.jdbc.common.AbstractDriver.connect(Unknown Source)
    at java.sql/java.sql.DriverManager.getConnection(DriverManager.java:681)
    at java.sql/java.sql.DriverManager.getConnection(DriverManager.java:229)
    at com.se.epx.dain.databricksqueryexecutor.DatabricksQueryExecutorTest.testReadUnityTableDataAsJson(DatabricksQueryExecutorTest.java:38)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:568)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
    at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
    at org.junit.runners.BlockJUnit4ClassRunner$1.evaluate(BlockJUnit4ClassRunner.java:100)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:366)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:103)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:63)
    at org.junit.runners.ParentRunner$4.run(ParentRunner.java:331)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329)
    at org.junit.runners.ParentRunner.access$100(ParentRunner.java:66)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293)
    at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:413)
    at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
    at org.junit.runner.JUnitCore.run(JUnitCore.java:115)
    at org.junit.vintage.engine.execution.RunnerExecutor.execute(RunnerExecutor.java:42)
    at org.junit.vintage.engine.VintageTestEngine.executeAllChildren(VintageTestEngine.java:80)
    at org.junit.vintage.engine.VintageTestEngine.execute(VintageTestEngine.java:72)
    at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:198)
    at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:169)
    at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:93)
    at org.junit.platform.launcher.core.EngineExecutionOrchestrator.lambda$execute$0(EngineExecutionOrchestrator.java:58)
    at org.junit.platform.launcher.core.EngineExecutionOrchestrator.withInterceptedStreams(EngineExecutionOrchestrator.java:141)
    at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:57)
    at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:103)
    at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:94)
    at org.junit.platform.launcher.core.DelegatingLauncher.execute(DelegatingLauncher.java:52)
    at org.junit.platform.launcher.core.SessionPerRequestLauncher.execute(SessionPerRequestLauncher.java:70)
    at org.eclipse.jdt.internal.junit5.runner.JUnit5TestReference.run(JUnit5TestReference.java:100)
    at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:40)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:529)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:756)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:452)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:210)
2

There are 2 best solutions below

2
Ethan Conrad On

We would need to see your DatabricksQueryExecutor class to provide a full solution, but DriverManager.getConnection() is a static method and as such you will want to mock it similar to the the examples in here:

https://www.baeldung.com/mockito-mock-static-methods

One of those examples is:

try (MockedStatic<StaticUtils> utilities = Mockito.mockStatic(StaticUtils.class)) {
        utilities.when(StaticUtils::name).thenReturn("Eugen");
        assertThat(StaticUtils.name()).isEqualTo("Eugen");
    }

You will want to replace StaticUtils with DriverManager and mock the getConnection method as above and return your mocked Connection object.

4
Lesiak On

The error occurs because you don't stub static DriverManager.getConnection in your test code.

Preferable way is to replace DriverManager with DataSource

See DriverManager javadoc

The DataSource interface, provides another way to connect to a data source. The use of a DataSource object is the preferred means of connecting to a data source.

Why is DataSource preferable?

DriverManager.getConnection is a static method, while DataSource is an interface.

This means that DriverManager.getConnection always goes to actual implementation and pulls a new connection.

DataSource, being an interface, can be abstracted over, both in tests and in prod.

In tests:

  • replace your DataSource with a mock (you dont have to use mockStatic, which is not very convenient, in terms of syntax and being thread local, and in the past was not available in Mockito)

In production, common use cases are:

  • connection pooling
  • logging

In all cases, you should pass DataSource to your service via constructor.