测试Opencsv CSVReader异常处理时Mock失效,求解决方法
问题分析
你的测试未生效的核心原因是:业务类MyCsvReader在构造函数内部直接实例化了CSVReader,测试中创建的mock对象和业务类实际使用的csvReader是完全独立的两个实例。mock的行为只会作用于你创建的mock对象,无法影响业务类内部自行创建的CSVReader实例,所以readNext()方法会正常读取数据,不会抛出预期异常。
解决方案
推荐通过依赖注入重构代码,为MyCsvReader提供注入CSVReader的入口,这样测试时可以传入mock对象。
方案一:构造函数注入(推荐)
修改MyCsvReader,新增一个直接接受CSVReader的构造函数,原有构造函数保留供业务代码使用:
import com.opencsv.CSVReader; import com.opencsv.CSVReaderBuilder; import com.opencsv.exceptions.CsvValidationException; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.nio.charset.StandardCharsets; import java.util.Arrays; public class MyCsvReader { private CSVReader csvReader; // 测试专用构造函数:直接注入CSVReader public MyCsvReader(CSVReader csvReader) { this.csvReader = csvReader; } // 业务用构造函数:根据字符串创建CSVReader public MyCsvReader(final String contentsAsString) { InputStream inputStream = new ByteArrayInputStream(contentsAsString.getBytes()); InputStreamReader inputStreamReader = new InputStreamReader(inputStream, StandardCharsets.UTF_8); CSVReader reader = new CSVReaderBuilder(inputStreamReader) .withSkipLines(0) .withKeepCarriageReturn(false) .build(); this.csvReader = reader; } public void readData() { String[] line; try { while ((line = csvReader.readNext()) != null) { System.out.println("line:" + Arrays.toString(line)); } } catch (IOException e) { System.out.println("got IOException"); throw new RuntimeException(e); } catch (CsvValidationException e) { System.out.println("got CsvValidationException"); throw new RuntimeException(e); } } }
对应的测试类修改为:
import com.opencsv.CSVReader; import org.junit.Test; import static org.junit.Assert.*; import static org.mockito.Mockito.*; import java.io.IOException; public class MyCsvReaderTest { @Test public void testException() throws Exception { // 创建mock的CSVReader并设定抛出异常 CSVReader mockCsvReader = mock(CSVReader.class); when(mockCsvReader.readNext()).thenThrow(new IOException("test")); // 传入mock对象创建MyCsvReader实例 MyCsvReader reader = new MyCsvReader(mockCsvReader); try { reader.readData(); fail("Expected an exception, but call succeeded"); } catch (RuntimeException e) { // 验证异常根源是否为预期的IOException assertTrue(e.getCause() instanceof IOException); assertEquals("test", e.getCause().getMessage()); } } }
方案二:反射替换(不推荐,仅作兼容参考)
如果无法修改构造函数,可以通过反射强行替换业务类内部的csvReader字段,但这种方式破坏封装性,仅作为临时兼容方案:
import com.opencsv.CSVReader; import org.junit.Before; import org.junit.Test; import java.lang.reflect.Field; import static org.junit.Assert.*; import static org.mockito.Mockito.*; import java.io.IOException; public class MyCsvReaderTest { private MyCsvReader reader; private CSVReader mockCsvReader; @Before public void setUp() throws Exception { String rowData = "column1,column2,column3\ntest,abc,def"; reader = new MyCsvReader(rowData); // 创建mock对象 mockCsvReader = mock(CSVReader.class); // 通过反射替换reader内部的csvReader字段 Field csvReaderField = MyCsvReader.class.getDeclaredField("csvReader"); csvReaderField.setAccessible(true); csvReaderField.set(reader, mockCsvReader); } @Test public void testException() throws Exception { when(mockCsvReader.readNext()).thenThrow(new IOException("test")); try { reader.readData(); fail("Expected an exception, but call succeeded"); } catch (RuntimeException e) { assertTrue(e.getCause() instanceof IOException); } } }
总结
测试无法mock内部实例的本质是代码耦合度太高,依赖注入是解决这类问题的标准方案:通过将依赖(如CSVReader)从外部传入类中,而非在类内部自行实例化,既能提升代码的可扩展性,也能让测试更轻松地替换依赖为mock对象。
内容的提问来源于stack exchange,提问作者alotia
相关产品推荐
相关产品推荐

