如何使用JMockit模拟Calendar.getInstance方法?

3

我正在尝试使用JMockit模拟Calendar.getInstance()方法。这是我第一次模拟任何东西。

下面是我的方法,它会以YYYYMMDD格式给出最近的星期一星期四的日期。

今天是星期日,所以它应该返回YYYYMMDD格式的星期四日期,因此它将是- 2014027

public static String getCorrectDate() {

    Calendar cal = Calendar.getInstance();

    try {
        int dow = cal.get(Calendar.DAY_OF_WEEK);
        switch (dow) {
        case Calendar.THURSDAY:
        case Calendar.FRIDAY:
        case Calendar.SATURDAY:
        case Calendar.SUNDAY:
        while (cal.get(Calendar.DAY_OF_WEEK) != Calendar.THURSDAY) {
            cal.add(Calendar.DATE, -1);
        }
        break;
        case Calendar.MONDAY:
        case Calendar.TUESDAY:
        case Calendar.WEDNESDAY:
        while (cal.get(Calendar.DAY_OF_WEEK) != Calendar.MONDAY) {
            cal.add(Calendar.DATE, -1);
        }
        break;
        }
    } catch (Exception ex) {
        // log error
    }

    return toDateFormat.format(cal.getTime());
}

所以我的问题是如何使用JMokcit模拟Calendar.getInstance方法,以便我可以轻松地对getCorrectDate方法进行单元测试? 我以前从未模拟过它,所以我遇到了一些问题?
如果可以,请任何人提供一个简单的示例,让我更好地理解吗?
我正在尝试像这样做,但它在MockClass上抱怨,因为它无法解析..我已经添加了JMockit的maven依赖项。
@MockClass(realClass = Calendar.class) 
class CalendarMock {
 private int hour;
 private int day;
 private int minute;

 public CalendarMock(int day, int hour, int minute) {
  this.hour = hour;
  this.day = day;
  this.minute = minute;
 }

 @Mock
 public int get(int id) {
  if (id == Calendar.HOUR_OF_DAY) {
   return hour;
  }
  if (id == Calendar.DAY_OF_WEEK) {
   return day;
  }

  if (id == Calendar.MINUTE) {
   return minute;
  }
  return -1;
 }
}

我不了解JMockIt,但是PowerMock可以模拟系统类。 - Martin Schröder
3个回答

4
public class CalendarTest {

    @Test
    public void testCalender() {

        new MockCalendar(1,2,3);

        Calendar c = Calendar.getInstance();
        int day = c.get(Calendar.HOUR_OF_DAY);
        Assert.assertEquals(day, 2);
    }

  public static class MockCalendar extends MockUp<Calendar> {
        private int hour;
        private int minute;
        private int day;

        public MockCalendar(int day, int hour, int minute) {
            this.hour = hour;
            this.minute = minute;
            this.day = day;
        }
        @Mock
        protected void $init() {}

        @Mock
        public static Calendar getInstance() {
            return new GregorianCalendar(1970,1,1);
        }

        @Mock
        public int get(int id) {
            if (id == Calendar.HOUR_OF_DAY) {
                return hour;
            }
            if (id == Calendar.DAY_OF_WEEK) {
                return day;
            }

            if (id == Calendar.MINUTE) {
                return minute;
            }
            return -1;
        }
    }
 }

}

你可以增强获取逻辑以执行操作。

返回一个GeorgianCalendar让我感到很神奇,谢谢Russel ;) - Skizo-ozᴉʞS ツ

1
与其在此处模拟静态方法,我强烈建议引入一个类似如下的Clock接口:
public interface Clock {
    Date now(); // Or long, or Instant if you're using Joda Time
}

然后,您可以拥有一个使用new Date()System.currentTimeMillis的生产实现,以及一个可以设置“当前时间”的测试实现。将您的时钟传递给该方法,或使其成为实例方法并在构造函数中“配置”该实例(例如,使用适当的时钟)。在我的经验中,Clock接口使您的代码更容易进行测试,而无需依赖于相对高级的模拟功能来模拟静态方法。

因此,您的生产代码将变为:

public static String getCorrectDate(Clock clock) {
    Calendar cal = new GregorianCalendar();
    cal.setTimeZone(...); // Which time zone are you interested in?
    cal.setTime(clock.now()); // Or cal.setTimeInMillis(clock.now());

    // ... logic as before
}

现在,您可以使用许多不同的日期和时间进行测试。

谢谢Jon。但是我该如何在我的当前代码中使用它?你能提供一个简单的例子吗? - user2467545
@SSH:你可以创建一个 GregorianCalendar 实例(假设你不想处理其他日历系统),并使用 setTimesetTimeInMillis 方法将其填充为当前日期/时间。你还应该考虑你感兴趣的时区。 - Jon Skeet

0
这是一个完整的测试类(不包括导入),用于测试getCorrectDate()方法,使用JMockit 1.6中提供的两个模拟API:
public class DateAdjustmentTest
{
    final int year = 2014;
    final int month = Calendar.MARCH;
    final Calendar[] datesOnDifferentWeekdays = {
        new GregorianCalendar(year, month, 2), // a sunday
        new GregorianCalendar(year, month, 3), // a monday
        new GregorianCalendar(year, month, 4), // a tuesday
        new GregorianCalendar(year, month, 5), // a wednesday
        new GregorianCalendar(year, month, 6), // a thursday
        new GregorianCalendar(year, month, 7), // a friday
        new GregorianCalendar(year, month, 8), // a saturday
    };

    @Test
    public void getAdjustedDateAccordingToWeekday_usingExpectationsAPI()
    {
        new NonStrictExpectations(Calendar.class) {{
            // Each consecutive call to this method will return the next date.
            // Calls to other Calendar methods will not be mocked.
            Calendar.getInstance(); result = datesOnDifferentWeekdays;
        }};

        assertEachDayOfWeekGetsCorrectlyAdjusted();
    }

    void assertEachDayOfWeekGetsCorrectlyAdjusted()
    {
        String thursdayOfPreviousWeek = "27/02/2014";
        assertEquals(thursdayOfPreviousWeek, getCorrectDate());

        String mondayOfSameWeek = "03/03/2014";
        assertEquals(mondayOfSameWeek, getCorrectDate());
        assertEquals(mondayOfSameWeek, getCorrectDate());
        assertEquals(mondayOfSameWeek, getCorrectDate());

        String thursdayOfSameWeek = "06/03/2014";
        assertEquals(thursdayOfSameWeek, getCorrectDate());
        assertEquals(thursdayOfSameWeek, getCorrectDate());
        assertEquals(thursdayOfSameWeek, getCorrectDate());
    }

    @Test
    public void getAdjustedDateAccordingToWeekday_usingMockUpAPI()
    {
        new MockUp<Calendar>() {
            @Mock
            Calendar getInstance(Invocation inv)
            {
                return datesOnDifferentWeekdays[inv.getInvocationIndex()];
            }
        };

        assertEachDayOfWeekGetsCorrectlyAdjusted();
    }
}

我认为这两个测试都很简单,而且不需要改变原始的生产代码。


我们可以使用Expectations(Calender.class)吗?为什么要将Calendar.class传递给NonStrictExpectations,我们可以使用NonStrictExpectations吗?它会部分模拟类(我认为是这样的)?请您帮我解释一下这些疑问。我正在使用JMockit 1.7。谢谢。 - वरुण
1
是的,Expectations(Calendar.class) 将起到相同的作用,只是您会有严格的期望,请记住这一点。上述两个测试都是对Calendar类进行部分模拟,因为我们只想让 getInstance() 返回(未模拟的)日历对象。有关更多信息和示例,请参阅在线文档 - Rogério

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接