2

I'm trying to write a unit test for some code that opens a database connection and performs some operations on the database. I want to assert that the connection is correctly closed, even if an Exception is thrown.

The code I want to test looks like this:

public void methodToTest(final String aName) {
  final String sqlDeleteStatement = "DELETE FROM " + DB_FULL_TABLE + " WHERE name=?";

  try (final Connection connection = this.dataSource.getConnection();
      final PreparedStatement deleteStatement = connection.prepareStatement(sqlDeleteStatement);) {
    connection.setAutoCommit(true);

    deleteStatement.setString(1, aName);
    deleteStatement.executeUpdate();
  }
  catch (final SQLException e) {
    // handle error
  }
}

I'm currently using jMock to create mock instances for the datasource and connection objects and simulate that an Exception is thrown in connection.prepareStatement:

public void testConnectionClosed() throws Exception {
  final Mockery mockery = new Mockery();
  final DataSource dataSource = mockery.mock(DataSource.class);
  final Connection connection = mockery.mock(Connection.class);

  final String exceptionMessage = "intentionally thrown " + UUID.randomUUID();

  mockery.checking(new Expectations() {{
      oneOf(dataSource).getConnection();
      will(returnValue(connection));

      oneOf(connection).prepareStatement(with(any(String.class)));
      will(throwException(new SQLException(exceptionMessage)));

      oneOf(connection).close();
    }});

  final ClassUnderTest cut = new ClassUnderTest(dataSource);
  cut.methodToTest("someName");

  mockery.assertIsSatisfied();
}

The problem I'm facing is, that the test is green with and without the expectation on connection.close(). Without the expectation I see a suppressed org.jmock.api.ExcpectationError:

Suppressed: unexpected invocation: java.sql.Connection1370903230.close()

But the test is not failing as the error is raised inside the implicit finally block of the try-with-resource statement.

I don't want to rewrite the code just to make this test meaningful, but I want to ensure proper resource handling as well without requiring too much knowledge about very specific implementation details like the usage of try-with-resource.

Is there a way to achieve this with jMock?


And no, I'm not asking for a recommendation on a library. So please don't mark as off topic.

GhostCat
  • 137,827
  • 25
  • 176
  • 248
dpr
  • 10,591
  • 3
  • 41
  • 71

2 Answers2

2

The key point here: you don't necessarily have to verify that try-with-resources is working as the Java Language Specification says it should work. In case you don't find a solution to this problem that works with jMock - then go for the "next best thing". And that would be: write a test case for the "good path" that ensures that the connection gets closed.

Meaning: your problem is that this test is throwing an exception, which means that the close call is "hidden" from you. But when you write a test where no exception is thrown, you should be able to verify that close() is called.

And as you are using try-with-resources, you can deduct that it will also be called for the bad path. Of course, that is not very elegant. But it is a pragmatic solution - rooted in the fact that you are using an "obscure" mocking framework!

Therefore, the real answer is: use a reasonable mocking framework.

This is somehow opinionated, but jmock is "not reasonable" for production usage - simply because "nobody" is using it. And it seems to be an "almost dead" project. When you turn to the jmock site you easily run into "dead" links. When you turn to their github presence - you find just a handful of commits since 2016. Just a few commiters anyway, and the number of commits very close to zero for lengthy periods of time.

When you rely on open source tooling to support your project/product, you want to make sure that there is a lively user and development community. Because when you run into problems, you need answers. And when you invest (by spending time to acquire the skills to use that tool) you want to avoid betting on a dead horse.

TL;DR:

  • either be pragmatic and only test the "good case"; hoping that nobody is "stupid" enough to turn try-with-resources into oldschool try/catch
  • change to a different mocking framework (for example mockito which has 20 times more questions tagged on SO than jmock)

( don't get me wrong: jmock might be a "fine" framework - but what matters is vitality. things that don't move (or move too slowly) are dead. you don't invest money in dead technology )

Given the fact that jmock is an established framework - simply consider "progress by evolution". Meaning: get approval for adding another mocking framework; and simply start using that for anything new. That is how we moved from EasyMock to Mockito; and that works pretty good.

GhostCat
  • 137,827
  • 25
  • 176
  • 248
  • I internally already raised concerns about jMock. Our test base is very large and jMock is used all over the place. As it is working fine for 99.9% of the use cases, it's hard to argue for a framework switch and get the effort approved. However would I be able to write a test like the above with mockito and get the desired behavior? – dpr Jul 19 '17 at 10:11
  • Regarding your point of testing the good-case and conducting that the error-case is handled correctly: If I did that I would add a lot of assumptions to the test that do not necessarily hold. If someone changes the implementation and decides not to use try-with-resource anymore but to close the connection in the try-block, the good-case would still succeed but the connection would remain unclosed in the error-case. That is the test would not discover the error in the described refactoring. – dpr Jul 19 '17 at 10:28
  • Sure. But if somebody intends to do **stupid** things on purpose, there isn't so much you can do. If that person has no idea what he is doing, he might as well go forward and "tune" that failing test until it gives you "green". And yes, writing up this testcase with Mockito or EasyMock should be straight forward. – GhostCat Jul 19 '17 at 10:34
  • @dpr Beyond that - see my updates regarding "getting rid of jmock". Final question: is there anything I could do to make my answer at least upvote-worthy? – GhostCat Jul 19 '17 at 10:35
  • 52.1k reputation and still going for every upvote? Here you go. Thanks for your answer! – dpr Jul 19 '17 at 11:02
  • @dpr Anything in life is about cost and return of investment. I spent *real* time on my answer. You found it helpful enough to ask *more* questions. So I think it is fair to cross-check ;-) ... and beyond that: unfortunately I am not a master in those tags that get so many views that even mediocre answers get you many votes. So I have to make sure to collect as much "return on investment" as possible. Otherwise you dont get to 52K ;-) – GhostCat Jul 19 '17 at 11:09
0

So I had a similar issue using Jmock and try-with-resources with CloseableHttpResponse and CloseableHttpClient. It's due to the internals of Jmock expecting the method ("close" in this case) only to be called via source (see What on earth is "Self-suppression not permitted" and why is Javac generating code which results in this error?).

You'll need to mock the close method on every closeable object you use. i.e. on both Connection and PreparedStatement.