Search code examples
javalambdajunitmockito

Extract Consumer variable fields while mocking Lambda expression


I have a method for batch updating a table.

Problem is when i try to call below block of code it is giving class cast exception,

        when(session.doWork(any())).thenAnswer(invocation -> {
        Consumer<Connection> consumer = invocation.getArgument(0);
        consumer.accept(mockConnection);
        return null;
    }); 
java.lang.ClassCastException: com.paypal.compliance.data.process.eftns.dao.impl.EFTReportsDAO$$Lambda$9/170106909 cannot be cast to java.util.function.Consumer

on debugging in intellij i found that invocation has 2 arguments arg$1 and arg$2 , arg$1 is the SQL statement and arg$2 is a list of EFTReportsDO , how to extract these individually ?

Below are the method and test case


@Transactional(value = "test", propagation = Propagation.REQUIRES_NEW)
        public void mergeAllEftReports(List<EFTReportsDO> instanceList) {
            String updateSql = "UPDATE EFT_XYZ " +
                    "SET " +
                    "COMMENTS=?, " +
                    "PP_BATCH_SOURCE=?, " +
                    "WHERE " +
                    "PP_BATCH_ID=? " +
                    "AND REPORT_ID=?";
    
            try {
                Session session = entityManager.unwrap(Session.class);
                session.doWork(connection -> {
                    try (PreparedStatement preparedStatement = connection.prepareStatement(updateSql)) {
                        for (EFTReportsDO entity : instanceList) {
                            preparedStatement.setString(1, entity.getComments());
                            preparedStatement.setString(2, entity.getPpBatchSource());
                    
                            preparedStatement.setString(3, entity.getId().getPpBatchId());
                            preparedStatement.setString(4, entity.getId().getReportId());
                            preparedStatement.addBatch();
                        }
                        preparedStatement.executeBatch();
                    }
                });
            } finally {
                entityManager.flush();
                entityManager.close();
            }
        }

     @Test
            public void testMergeAllEftReports() throws SQLException {
               // Arrange
                List<EFTReportsDO> instanceList = new ArrayList<>();
                EFTReportsDO eftReportsDO = new EFTReportsDO(); // create an instance of EFTReportsDO
                instanceList.add(eftReportsDO);
                when(entityManager.unwrap(Session.class)).thenReturn(session);
                Connection connection = mock(Connection.class);
                PreparedStatement preparedStatement = mock(PreparedStatement.class);
                when(connection.prepareStatement(anyString())).thenReturn(preparedStatement);
        
                // Act & Assert
                 when(session.doWork(any())).thenAnswer(invocation -> {
                Consumer<Connection> consumer = invocation.getArgument(0);
                consumer.accept(mockConnection);
                return null;
            });
                // Call the method under test
                eftReportsDAO.mergeAllEftReports(instanceList);
        
                verify(preparedStatement, times(1)).executeBatch();
        
                // Verify that flush and close methods are called on the entityManager
                verify(entityManager, times(1)).flush();
                verify(entityManager, times(1)).close();
        }


Solution

  • Your lambda cannot be cast to Consumer<Connection>, it must match the type of SAM type expected by Session.doWork, namely Work

    doAnswer((invocation) -> {
        Work jdbcWork = invocation.getArgument(0);
        jdbcWork.execute(connection);
        return null;
    }).when(session).doWork(any());
    

    Note: session.doWork returns void, so I used doAnswer - when, instead of when - thenAnswer

    In general, you cannot cast between 2 different SAM interfaces, even if their structure is identical:

    interface StringConsumer1 {
        void execute(String s1);
    }
    
    interface StringConsumer2 {
        void execute(String s1);
    }
    
    @Test
    void testLambdaConversionFails() {
        Throwable thrown = catchThrowable(() -> {
            StringConsumer1 l1 = (aaa) -> System.out.println(aaa);
            StringConsumer2 l2 = (StringConsumer2) l1;
        });
    
        assertThat(thrown).isInstanceOf(ClassCastException.class);
    
    }
    

    In your case Consumer<Connection> is not identical to Work (method name and checked exceptions differ)