我是Java/Mockito的新手,并试图测试Dao方法,特别是捕获ParseException并抛出SQLException的异常条件。
这是道码:
public Template saveTemplate(Template template) throws SQLException {
logger.debug("Saving template details into the db ", template.getTemplateName());
SimpleDateFormat dt = new SimpleDateFormat("yyyyy-mm-dd hh:mm:ss");
Long date = 0L;
try {
date = dt.parse(template.getStartTime()).getTime();
} catch (ParseException e) {
throw new SQLException("Error while processing date " + template.getTemplateName());
}
Long finalDate = date;
我的策略是模拟SimpleDateFormat.parse()
调用,以便它抛出 ParseException,但这不起作用。甚至不确定这是一个好的策略...
首先我尝试了:
@InjectMocks private SimpleDateFormat simpleDateformat;
但这不起作用,因为 SimpleDateFormat 构造函数需要一个参数,并得到错误:
org.mockito.exceptions.base.MockitoException:
Cannot instantiate @InjectMocks field named 'simpleDateFormat' of type 'class java.text.SimpleDateFormat'.
You haven't provided the instance at field declaration so I tried to construct the instance.
However the constructor or the initialization block threw an exception : null
然后我尝试了这个:
@Mock
private SimpleDateFormat simpleDateFormat;
@Test(expected = SQLException.class)
public void test_save_template_date_parse_error() throws ParseException, SQLException {
initMocks(this);
Mockito.mockingDetails(simpleDateFormat);
Mockito.when(simpleDateFormat.parse(anyString(),new ParsePosition(anyInt()))).thenThrow(new ParseException(anyString(),anyInt()));
Template template = new Template();
template.setStartTime("2017-01-02 12:12:12");
template.setTemplateId(1);
given(jdbcTemplate.getJdbcOperations()).willReturn(jdbcOperations);
templateDAOImpl.saveTemplate(template);
}
由此产生的错误对我未经实践的眼睛没有帮助:
org.mockito.exceptions.misusing.InvalidUseOfMatchersException:
Misplaced or misused argument matcher detected here:
-> at com.macys.etap.ee.dao.TemplateDAOImplTest.test_save_template_date_parse_error(TemplateDAOImplTest.java:77)
-> at com.macys.etap.ee.dao.TemplateDAOImplTest.test_save_template_date_parse_error(TemplateDAOImplTest.java:77)
You cannot use argument matchers outside of verification or stubbing.
Examples of correct usage of argument matchers:
when(mock.get(anyInt())).thenReturn(null);
doThrow(new RuntimeException()).when(mock).someVoidMethod(anyObject());
verify(mock).someMethod(contains("foo"))
This message may appear after an NullPointerException if the last matcher is returning an object
like any() but the stubbed method signature expect a primitive argument, in this case,
use primitive alternatives.
when(mock.get(any())); // bad use, will raise NPE
when(mock.get(anyInt())); // correct usage use
Also, this error might show up because you use argument matchers with methods that cannot be mocked.
Following methods *cannot* be stubbed/verified: final/private/equals()/hashCode().
Mocking methods declared on non-public parent classes is not supported.
那么我如何模拟这个东西并抛出错误呢?
编辑:建议的新方法,模拟模板.getStartTime((:
@Test(expected = SQLException.class)
public void test_save_template_date_parse_error() throws ParseException, SQLException {
initMocks(this);
Template templateMock = Mockito.mock(Template.class);
Mockito.when(templateMock.getStartTime()).thenReturn("invalid");
Mockito.mockingDetails(templateMock.getStartTime());
Template template = new Template();
template.setStartTime("2017-01-02 12:12:12");
template.setTemplateId(1);
given(jdbcTemplate.getJdbcOperations()).willReturn(jdbcOperations);
// Fixed per @Daniel Pryden : works now
templateDAOImpl.saveTemplate(templateMock);
}
现在可以使用修复程序。
在我看来,你在这里甚至不需要Mockito,你可以简单地执行以下操作:
Template template = new Template();
template.setStartTime("THIS IS AN INVALID DATE");
template.setTemplateId(1);
templateDAOImpl.saveTemplate(template);
然后SQL感知将被抛出。
SimpleDateFormat 根本无法被模拟,因为您正在方法中创建一个新实例,因此永远不会应用模拟。
可能性:
- 更改类结构(例如,将 SimpleDateFormat 作为构造器参数,然后 InjectMocks 注释将起作用(
- 为解析方法传递无效数据以破坏它
- 使用 PowerMockito 当新方法,但它应该是最终的
模拟不是魔法。它们只是对象,因此它们遵循与 Java 语言中所有其他对象相同的规则。最重要的是:模拟对象不会神奇地取代真实对象。您需要传递对模拟的引用,而不是对真实对象的引用。
在更新的问题中,您显示以下代码:
Template templateMock = Mockito.mock(Template.class);
Mockito.when(templateMock...)...
Template template = new Template();
...
templateDAOImpl.saveTemplate(template);
也就是说:您正在设置一个名为templateMock
的对象,该对象具有Template
类型,并配置了您想要的行为,但您实际传递到templateDAOImpl.saveTemplate
的对象不是该对象!
这意味着所有设置templateMock
的代码实际上是死代码:由于您传递给saveTemplate
的值是使用new Template()
构造的,因此它不是模拟。
更一般地说:Mockito从不做任何你自己做不到的事情。例如,在这种情况下,您可以简单地创建自己的Template
子类:
private static class FakeTemplate extends Template {
@Override
public String getStartTime() {
return "invalid date";
}
// override other methods as necessary/desired
}
// in your test:
Template fakeTemplate = new FakeTemplate();
templateDAOImpl.saveTemplate(fakeTemplate);
这就是莫克蒂托在你嘲笑Template
时所做的一切。Mockito只是以一种"更花哨"的方式做到这一点,这样你就有更多的样板代码要写了。但是,如果您不了解Mockito在做什么,那么您就不应该被迫使用它。始终编写您理解的代码,然后如果出现问题,您将始终能够对其进行调试。
(题外话:有一些肮脏的黑客可以让new
运算符实例化一些与编译时不同的东西——这就是PowerMockito发挥其"魔力"的方式——但这从来都不是必需的,我永远不会推荐它。
只是为了记录,我找到了另一种实现此要求的方法,我使用了间谍并嘲笑了几乎所有内容; 它可能会帮助其他人。
@InjectMocks
final Template partiallyMockedTemplate = spy(new Template());
@Test(expected = SQLException.class)
public void test_save_template_date_parse_error() throws SQLException {
initMocks(this);
Template template = new Template();
doReturn("2018-05-44").when(partiallyMockedTemplate).getStartTime();
partiallyMockedTemplate.setStartTime("2017-01-02 12:12:12");
partiallyMockedTemplate.setTemplateId(1);
given(jdbcTemplate.getJdbcOperations()).willReturn(jdbcOperations);
templateDAOImpl.saveTemplate(partiallyMockedTemplate);
}