Reputation: 2424
scuevals_api/resources/students.py:
def year_in_range(year):
return datetime.now().year <= year <= datetime.now().year + 10
class StudentsResource(Resource):
args = {
'graduation_year': fields.Int(required=True, validate=year_in_range),
}
...
I'm trying to mock year_in_range
(to always return True) however, all my attempts have failed so far.
I'm using the decorator approach with mock.patch
and have tried a ton of different targets, but the one I believe should be the correct one is:
@mock.patch('scuevals_api.resources.students.year_in_range', return_value=True)
The mock function never gets called, as in, it's not mocking correctly. I'm not getting any errors either.
My only remaining suspicions is that it has something to do with that the function is passed in to fields.Int
as a param (hence the question title), but in my head, it shouldn't affect anything.
I'm clueless as to where this function should be mocked?
Upvotes: 2
Views: 140
Reputation: 2424
Thanks to the explanation by Chris Hunt, I came up with an alternative solution. It does modify the application code rather than the testing code, but if that is acceptable (which, in today's day and age probably should be, since having testable code is high priority), it is a really simple solution:
It's not possible to mock year_in_range
since a reference to the original function is saved before the mocking is done. Therefore, "wrap" the function you want to mock with another function and pass the wrapper instead. Wrapping can be done in a nice and tidy way using lambda functions:
def year_in_range(year):
return datetime.now().year <= year <= datetime.now().year + 10
class StudentsResource(Resource):
args = {
'graduation_year': fields.Int(required=True, validate=lambda y: year_in_range(y)),
}
...
Now, when I mock year_in_range
as stated in the question, it will work. The reason is because now a reference is saved to the lambda function, instead of to the original year_in_range
(that won't be accessed until the lambda function runs, which will be during the test).
Upvotes: 1
Reputation: 4030
By the time mock
has patched year_in_range
it is too late. mock.patch
imports the module specified by the string you provided and patches the name indicated within the module so it refers to a mock object - it does not fundamentally alter the function object itself. On import of scuevals_api.resources.students
the body of the StudentsResource
class will be executed and a reference to the original year_in_range
saved within the StudentResource.args['graduation_year']
object, as a result making the name year_in_range
refer to a mock object has no impact.
In this particular case you have a few options:
year_in_range
you can seed the database (?) with data that tests the conditiondatetime.now
which will be called by year_in_range
StudentResource.args['graduation_year']
where the function passed to validate
has been saved.Upvotes: 2