Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I use mocking to test a next_day_of_week function

I'm tracking events that recur on a particular day of the week (e.g., first Sunday of the month, third Friday of the month). I have a DayOfWeek model that stores the event's day of the week. It contains a method next_day_of_week to return a date object set to the next occurrence of whatever weekday a given event instance is set to (this helps with figuring out when the next occurrence of an event is).

For example, on Sunday 7/3/2011:

  • For an object with DayOfWeek set to Sunday, next_day_of_week would return 7/3/2011.
  • For DayOfWeek set to Monday, it would return 7/4/2011.
  • For DayOfWeek set to Saturday, it would return 7/9/2011.

And so on. I am writing unit tests (my first ever; did I mention I'm pretty new to this stuff?) and trying to wrap my head around how to test this method. I know I need to mock something, but I'm not quite sure what. This question seems to get at what I'm asking: Python: Trying to mock datetime.date.today() but not working

So I try to mock out datetime.date in tests.py:

class FakeDate(date):
"A fake replacement for date that can be mocked for testing."
    def __new__(cls, *args, **kwargs):
        return date.__new__(date, *args, **kwargs)

And I create my test case, patching in the mock class and setting today to 7/3/2011:

class TestDayOfWeek(TestCase):
    """Test the day of the week functions."""

    @mock.patch('datetime.date', FakeDate)
    def test_valid_my_next_day_of_week_sameday(self):
        from datetime import date
        FakeDate.today = classmethod(lambda cls: date(2011, 7, 3)) # July 3, 2011 is a Sunday
        new_day_of_week = DayOfWeek.objects.create()
        new_day_of_week.day = "SU"
    self.assertEquals(new_day_of_week.my_next_day_of_week(), date(2011, 7, 3))

For reference, here is the model class:

class DayOfWeek(ModelBase):
"""
Represents a day of the week (on which an event can take place). 
Because the dates of these events are often designated by terms like 'first Monday'
or 'third Friday', this field is useful in determining on which dates individual
readings take place.
"""

# The pk in the db is 1-indexed (Monday=1, Tuesday=2, etc), but python's days 
# of the week are 0-indexed if you use .weekday(), so we are using .isoweekday()
# instead. This list is used in my_next_day_of_week.
days =[ 'No day', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday',
       'Sunday' ]

DAYS_OF_WEEK_CHOICES = (
('MO', days[1]),
('TU', days[2]),
('WE', days[3]),
('TH', days[4]),
('FR', days[5]),
('SA', days[6]),
('SU', days[7]),
)

day = models.CharField(max_length=2, choices=DAYS_OF_WEEK_CHOICES)

def __unicode__(self):
    for daypair in self.DAYS_OF_WEEK_CHOICES:
        if self.day in daypair:
            return daypair[1]

    # This shouldn't happen
    raise InvalidDayOfWeekError

# my_next_day_of_week returns a datetime equal to the start (midnight+min) of the next day that is this instance's day of the week.
# It doesn't know what time the event is, so if today is the day of the week the event falls on,
# it simply returns today.
def my_next_day_of_week(self):
    """ 
    Returns a datetime equal to the start of the next day that is this instance's day of the week. 
    """

    today_day = date.today().isoweekday() # Find the number of the current day of the week
    reading_day = self.days.index(self.__unicode__()) # Find the number of the instance's day of the week
            # There is probably a more pythonic way to do this next part
    next_day = date.today() # start with next day = today
    while next_day.isoweekday() != reading_day:
        next_day = next_day + timedelta(1)

    return next_day

So when I run django's test runner, the test fails because my DayOfWeek instance seems not to use the mock datetime.date and instead sees today's actual day. From my reading, I understand that the mock only exists within the test method, not before or after. But does that also mean it does not exist for any objects/methods that are instantiated/called from within the test method? Then what is the use of it? I don't think that's the problem, but rather that I am doing something wrong when patching. Maybe a problem with namespaces? I am reading this: http://www.voidspace.org.uk/python/mock/patch.html#id2 I will keep trying to fix it and will edit this if I succeed, but until then any pointers are appreciated!

EDIT: Realized that I was using datetime.datetime in my model instead of datetime.date. I fixed that and edit the code above, but the underlying problem of the mocked class not being used remains.

like image 806
sandinmyjoints Avatar asked Jul 04 '11 20:07

sandinmyjoints


People also ask

What is a mocking testing?

In mock testing, the dependencies area unit is replaced with objects that simulate the behavior of the important ones. It is based upon behavior-based verification. The mock object implements the interface of the real object by creating a pseudo one. Thus, it's called mock.

How do you mock a function?

There are two ways to mock functions: Either by creating a mock function to use in test code, or writing a manual mock to override a module dependency.

How does mocking work in Python?

mock provides a powerful mechanism for mocking objects, called patch() , which looks up an object in a given module and replaces that object with a Mock . Usually, you use patch() as a decorator or a context manager to provide a scope in which you will mock the target object.


1 Answers

Figured it out.

The question was really Where to patch and the answer came from studying the Mock documentation I linked to above (this page).

The solution was to mock the date class in the namespace of the module that contains the models for this app, like so:

class TestDayOfWeek(TestCase):
    #Test the day of the week functions.

    # mock out the date class in the module that has already imported it via
    # from datetime import date, i.e. series.models (app_name.module_name)
    @mock.patch('series.models.date', FakeDate)
    def test_valid_my_next_day_of_week_sameday(self):
        from datetime import date
        FakeDate.today = classmethod(lambda cls: date(2011, 7, 3)) # July 3, 2011 is a Sunday

        new_day_of_week = DayOfWeek.objects.create()
        new_day_of_week.day = "SU"
        self.assertEquals(new_day_of_week.my_next_day_of_week(), date(2011, 7, 3))

Hopefully this can be of help to someone else!

like image 82
sandinmyjoints Avatar answered Nov 11 '22 12:11

sandinmyjoints