Search code examples
javajunitmockingjmockitexpectations

JMockit is not instantiating captured interfaces in Expectations block


I'm not sure whether I'm using JMockit incorrectly, or there's something amiss in my setup. I'm using JMockit 1.32 with JUnit 4.12 in Eclipse.

My problem seems to be that interfaces aren't being captured. Specifically in the java.sql package. For example:

public class Dto {
    private int id;

    public Dto(){}

    public Dto(ResultSet rs) {
        try {
            id = rs.getInt(1);
        } catch (SQLException e) { }
    }

    public int getId() {return id;}
    void setId(int id) {this.id = id;}
}

.

public class ClassUnderTest {
    public static Dto loadObject(Connection conn, String tablename, int id) {
        Dto result = null;
        ResultSet rs = null;
        PreparedStatement ps = null;

        try {
            String sql = "select * from " + tablename + " where id = ?";
            ps = conn.prepareStatement(sql);
            ps.setInt(1, id);

            rs = ps.executeQuery();
            if (rs.next()) {
                result = new Dto(rs);
            }

        } catch (SQLException e) {  
        } finally {
            try {
                if (ps != null) ps.close();
            } catch (SQLException e) { }
        }

        return result;
    }
}

.

public class ResultSetTest  extends junit.framework.TestCase {

    private static final int OBJ_ID = 5;

    @Capturing
    ResultSet mockResultSet;

    @Capturing
    PreparedStatement mockStatement;

    @Capturing
    Connection mockConn;



    @Test
    public void testGetDtoById() throws SQLException {

        new Expectations() {{
            mockConn.prepareStatement(anyString);  result = mockStatement;
            mockStatement.setInt(anyInt, OBJ_ID);
            mockResultSet.next();                  result = true;
            new Dto(mockResultSet);                result = new Dto();
            mockResultSet.next();                  result = true;
        }};

        Dto dto = ClassUnderTest.loadObject(mockConn, "", OBJ_ID);

        assertEquals(dto.getId(), OBJ_ID);    
    }
}

In this setup, test execution fails with a NPE on the first line in the Expectations(){} block. But from the tutorials, etc. I'm expecting a mocked instance to have been created. (e.g. tutorial)

Trying to move past this, I created explicit mocked classes like so:

public class ResultSetMockup extends MockUp<ResultSet> { }

public class PreparedStatementMockup extends MockUp<PreparedStatement>
{    
    @Mock ResultSet executeQuery() {return new ResultSetMockup().getMockInstance();}
}

public class ConnectionMockup extends MockUp<Connection> 
{
    @Mock PreparedStatement prepareStatement(String sql) throws SQLException { 
        return new PreparedStatementMockup().getMockInstance(); 
    }
}

@Capturing
ResultSet mockResultSet = new ResultSetMockup().getMockInstance();

@Capturing
PreparedStatement mockStatement = new PreparedStatementMockup().getMockInstance();

@Capturing
Connection mockConn = new ConnectionMockup().getMockInstance();

At this point, the Expectations() {} block is happy, but it appears that results is never actually being set. By setting a breakpoint I see that rs.next() always fails. So I presume nothing is actually being captured.

What am I doing wrong? Or is something in my setup preventing JMockit from actually running?


Solution

  • The actual problem in the test is that it's mixing the APIs from JUnit 3 (anything from junit.framework) and JUnit 4+ (org.junit). This should never be done.

    JMockit only supports the JUnit 4 API, not the obsolete JUnit 3 API. So, simply remove "extends from junit.framework.TestCase" that it will be ok.

    BTW, your Java IDE should have warned against this mistake. IntelliJ, at least, promptly displays "Method 'testGetDtoById()' annotated with '@Test' inside class extending JUnit 3 TestCase".

    Also, the test (and the code under test) has several other mistakes...