How to mock the default constructor of the Date class with JMockit?
I want to mock the default constructor of java.util.date
so it does not construct
a Date
object representing the time when it was created, but always the same Date
object (in my example below 31 Dec 2010). I tried doing this with JMockit
and JUnit
, but when executing my test below, the output is always Thu Jan 01 01:00:00 CET 1970
. So what is wrong with my mock of Date()
?
import java.util.Date;
import org.junit.*;
import mockit.*;
public class AppTest {
@Before
public void setUp() {
Mockit.setUpMocks(MockedDate.class);
}
@After
public void tearDown() {
Mockit.tearDownMocks();
}
@Test
public void testDate() {
Date today=new Date();
System.out.println(today.toString());
}
@MockClas开发者_Go百科s(realClass=Date.class)
public static class MockedDate {
@Mock
public void $init() {
// Now should be always 31.12.2010!
new Date(110,11,31); //110 = 2010! 11 = December! This is sick!
}
}
}
al nik's answer was a good hint for me. It is better to mock the System
class instead of the Date
class to generate a fake time. My own solution in the end was simply to mock the System.currentTimeMillis()
method (this method is called by Date()
internally).
JMockit 1.5 and later
new MockUp<System>(){
@Mock
public long currentTimeMillis() {
// Now is always 11/11/2011
Date fake = new Date(111,10,11);
return fake.getTime();
}
};
JMockit 1.4 and earlier
@MockClass(realClass = System.class)
public static class MockedSystem {
@Mock
public long currentTimeMillis() {
// Now is always 11/11/2011
Date fake = new Date(111,10,11);
return fake.getTime();
}
}
As suggested in the Test Driven book it's good practice to use a SystemTime abstraction in your java classes. Replace your method calls (System#currentTimeMillis and Calendar#getInstance) and direct construction (new Date()) with static method calls like:
long time = SystemTime.asMillis();
Calendar calendar = SystemTime.asCalendar();
Date date = SystemTime.asDate();
To fake the time you just need to modify what's returned by your SystemTime class.
SystemTime use a TimeSource interface that by default delegates to System.currentTimeMillis()
public interface TimeSource {
long millis();
}
a configurable SystemTime implementation could be something like this
public class SystemTime {
private static final TimeSource defaultSrc =
new TimeSource() {
public long millis() {
return System.currentTimeMillis();
}
};
private static TimeSource source = null;
public static long asMillis() {
return getTimeSource().millis();
}
public static Date asDate() {
return new Date(asMillis());
}
public static void reset() {
setTimeSource(null);
}
public static void setTimeSource(TimeSource source) {
SystemTime.source = source;
}
private static TimeSource getTimeSource() {
return (source != null ? source : defaultSrc);
}
}
and to fake the returned time you simply do
@Test
public void clockReturnsFakedTimeInMilliseconds() throws Exception {
final long fakeTime = 123456790L;
SystemTime.setTimeSource(new TimeSource() {
public long millis() {
return fakeTime;
}
});
long clock = SystemTime.asMillis();
assertEquals("Should return fake time", fakeTime, clock);
}
Joda-Time library simplifies working with dates in Java and offers you something like this out of the box
You mocked the constructor, and inside you made an instance of Date (that has nothing to do with the one constructed) and just threw it away. Since the default constructor is mocked out, date is not initialized to the current time, and so you get the time of zero (which represents 1970-01-01).
To modify the returned date you need to use a magic "it" attribute, like so:
@MockClass(realClass=Date.class)
public static class MockedDate {
public Date it;
@Mock
public void $init() {
// This is sick!
it.setDate(31);
it.setYear(110); // 110 = 2010!
it.setMonth(11); // 11 = December!
}
}
And here's a complete JUnit example based on @asmaier's great answer:
@Test
public void dateConstructorReturnsMockedDate() throws ParseException {
final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
final Date mockDate = dateFormat.parse("2002-02-02");
new MockUp<System>(){
@Mock
public long currentTimeMillis() {
return mockDate.getTime();
}
};
final Date actualDate = new Date();
assertThat(actualDate).isEqualTo(mockDate); // using AssertJ
}
When using Maven, configure JMockit as follows in pom.xml
:
<dependencies>
<dependency>
<groupId>org.jmockit</groupId>
<artifactId>jmockit</artifactId>
<version>${jmockit.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>${maven-surefire-plugin.version}</version>
<configuration>
<argLine>
-javaagent:${settings.localRepository}/org/jmockit/jmockit/${jmockit.version}/jmockit-${jmockit.version}.jar
</argLine>
<disableXmlReport>true</disableXmlReport>
</configuration>
</plugin>
</plugins>
</build>
<properties>
<jmockit.version>1.44</jmockit.version>
</properties>
精彩评论