可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
I have a function that uses the current time to make some calculations. I'd like to mock it using mockito.
An example of the class I'd like to test:
public class ClassToTest { public long getDoubleTime(){ return new Date().getTime()*2; } }
I'd like something like:
@Test public void testDoubleTime(){ mockDateSomeHow(Date.class).when(getTime()).return(30); assertEquals(60,new ClassToTest().getDoubleTime()); }
Is it possible to mock that? I wouldn't like to change the "tested" code in order to be tested.
回答1:
The right thing to do is to restructure your code to make it more testable as shown below. Restructuring your code to remove the direct dependency on Date will allow you to inject different implementations for normal runtime and test runtime:
interface DateTime { Date getDate(); } class DateTimeImpl implements DateTime { @Override public Date getDate() { return new Date(); } } class MyClass { private final DateTime dateTime; // inject your Mock DateTime when testing other wise inject DateTimeImpl public MyClass(final DateTime dateTime) { this.dateTime = dateTime; } public long getDoubleTime(){ return dateTime.getDate().getTime()*2; } } public class MyClassTest { private MyClass myClassTest; @Before public void setUp() { final Date date = Mockito.mock(Date.class); Mockito.when(date.getTime()).thenReturn(30L); final DateTime dt = Mockito.mock(DateTime.class); Mockito.when(dt.getDate()).thenReturn(date); myClassTest = new MyClass(dt); } @Test public void someTest() { final long doubleTime = myClassTest.getDoubleTime(); assertEquals(60, doubleTime); } }
回答2:
If you have legacy code that you cannot refactor and you do not want to affect System.currentTimeMillis()
, try this using Powermock
and PowerMockito
//note the static import import static org.powermock.api.mockito.PowerMockito.whenNew; @PrepareForTest({ LegacyClassA.class, LegacyClassB.class }) @Before public void setUp() throws Exception { SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); sdf.setTimeZone(TimeZone.getTimeZone("PST")); Date NOW = sdf.parse("2015-05-23 00:00:00"); // everytime we call new Date() inside a method of any class // declared in @PrepareForTest we will get the NOW instance whenNew(Date.class).withNoArguments().thenReturn(NOW); } public class LegacyClassA { public Date getSomeDate() { return new Date(); //returns NOW } }
回答3:
You could do this by using PowerMock, which augments Mockito to be able to mock static methods. You could then mock System.currentTimeMillis()
, which is where new Date()
ultimately gets the time from.
You could. I'm not going to advance an opinion on whether you should.
回答4:
One approach, that does not directly answer the question but might solve the underlying problem (having reproducible tests), is allow the Date
as an parameter for tests and add a delegate to the default date.
Like so
public class ClassToTest { public long getDoubleTime() { return getDoubleTime(new Date()); } long getDoubleTime(Date date) { // package visibility for tests return date.getTime() * 2; } }
In production code, you use getDoubleTime()
and test against getDoubleTime(Date date)
.