I am writing unit tests with Spock for a series of nested objects. The code I'm writing tests for is quite legacy and doesn't use dependency injection. However it is also quite mission-critical so I'd rather not touch it unless I really need to.
Here is the constructor of the class I'm trying to test:
public SqlTable(Connection conn, String query) throws Exception {
this.statement = conn.createStatement();
this.resultSet = statement.executeQuery(query);
meta = resultSet.getMetaData();
int n = meta.getColumnCount();
columns = new Column[n];
for (int c = 0; c < n; c ) {
columns[c] = new Column(meta.getColumnName(c 1));
// ...
}
}
In the tests I stub the nested mocks in a .groovy
file, like this:
def "initialising a SQL table"() {
given:
def COL_NAME = "someColumnName"
def mockResSetMeta = Mock(ResultSetMetaData) {
getColumnCount() >> 1
getColumnName(_ as int) >> COL_NAME
}
and:
def mockResSet = Mock(ResultSet) {
getMetaData() >> mockResSetMeta
}
and:
def mockStatement = Mock(Statement) {
executeQuery(_ as String) >> mockResSet
}
and:
def mockConn = Mock(Connection) {
createStatement() >> mockStatement
}
when: "SqlTable object"
def table = new SqlTable(mockConn, "some query")
then: "the table contains the categorical column"
table.columns[0].getName() == COL_NAME
}
However the test fails. By debugging, I found that, in the SqlTable
constructor, the mock for the ResultSetMetaData
object, when getColumnName()
is called, always returns null
.
I did some digging, and it seems like this is due to how stubbing and mocking are handled together by Spock. I found two promising answers on SO:
However for the life of me I wasn't able to modify the test in order to make it work.
CodePudding user response:
The Problem lies in this line getColumnName(_ as int) >> COL_NAME
it works when you change it to getColumnName(_ as Integer) >> COL_NAME
or just getColumnName(_) >> COL_NAME
.
My current assumptions as to why this is happening, is that the actual arguments in the mock are passed as an Object[]
and that can't contain primitive types.
Here is a runnable reproducer
import spock.lang.*;
import java.sql.*;
class ASpec extends Specification {
def "initialising a SQL table"() {
given:
def COL_NAME = "someColumnName"
def mockResSetMeta = Mock(ResultSetMetaData) {
getColumnCount() >> 1
getColumnName(_ as Integer) >> COL_NAME
}
and:
def mockResSet = Mock(ResultSet) {
getMetaData() >> mockResSetMeta
}
and:
def mockStatement = Mock(Statement) {
executeQuery(_ as String) >> mockResSet
}
and:
def mockConn = Mock(Connection) {
createStatement() >> mockStatement
}
when: "SqlTable object"
def table = new SqlTable(mockConn, "some query")
then: "the table contains the categorical column"
table.columns[0].getName() == COL_NAME
}
}
@groovy.transform.Canonical
class Column {
String name
}
class SqlTable {
def columns
public SqlTable(Connection conn, String query) throws Exception {
def statement = conn.createStatement();
def resultSet = statement.executeQuery(query);
def meta = resultSet.getMetaData();
int n = meta.getColumnCount();
columns = new Column[n];
for (int c = 0; c < n; c ) {
columns[c] = new Column(meta.getColumnName(c 1));
// ...
}
}
}
Try it in the Groovy Web Console
For future reference, I easily detect the issue by actually performing mocking and not just stubbing.
then:
1 * mockResSetMeta.getColumnName(_ as int) >> COL_NAME
will print
1 * mockResSetMeta.getColumnName(_ as int) >> COL_NAME (0 invocations)
Unmatched invocations (ordered by similarity):
1 * mockResSetMeta.getColumnName(1)
One or more arguments(s) didn't match:
0: argument instanceof int
| | |
| false int
1 (java.lang.Integer)