vendredi 29 mai 2015

How to have clean default mock variables values before each test

I'm writing unit tests for Django projects using: unittest and mock.

Evertyhing looks ok, but the problem appears with a large number of patches for each model being used in the action. I'm usually creating around 10 tests for each method under the test to be able to verify all it's flows. This means that multiple tests will have the same mocked methods with small changes. Instead of creating patches for each method separately I've created setUpClass method to define patches once for all the tests, like this:

@classmethod
def setUpClass(cls):
    # Patching models to remove calls to DB
    patched_models = {'actions': ['Model1', 'Model2', 'Model3']}
    cls.patchers = []
    for location in patched_models:
        for model in patched_models[location]:
            patcher = patch('application.{0}.'.format(location) + model, spec = eval(model))
            cls.patchers.append(patcher)
            model_mock = patcher.start()
            model_mock.objects = mock.Mock()
            model_mock.objects.get = getattr(cls, '_get_' + model.lower(), None)
            model_mock.objects.filter = getattr(cls, '_filter_' + model.lower(), None)

This allows me to create methods like:

@classmethod
def _get_model1(cls, **kwargs):
    return mock.Mock(name='Model1')

Now, whenever in code under test I have something like Model1.objects.get(pk = request.id) I'm receiving response from my function. This part looks good. The problem occurs whenever I try to change some parameters of the mock that is being returned inside def _get_model1. Let's say I want in 5 tests from 10 I need to return mock with name equals Model1 and in remaining 5 tests with the same function I want to return mock that has name Model1.1.

The idea that I got was to have somewhere default parameter defined that will be reset before each test execution, like this:

def setUp(self):
    # Setting default value
    self.__class__.model_name = 'Model1'

And getter function will transform in something like this:

@classmethod
def _get_model1(cls, **kwargs):
    return mock.Mock(name = cls.model_name)

Having this, will always return us mock with the default name. And now, if in some test I need different name I will do something like this:

def test_another_name(self):
    self.__class__.model_name = 'Model1.1'
    # Execute some code with a new name

Somehow, this code doesn't work and I'm still receiving each time the Mock with default value.

Do you know why do I always receive default values or maybe you could suggest better way of doing the same thing with default values instead of defining each value explicitly inside each test?

Aucun commentaire:

Enregistrer un commentaire