3

i'm quite new to python and mocking generally. So I tried to find a solution to my problem, through reading the documentation: https://docs.python.org/3/library/unittest.mock.html#magic-methods, following article: http://alexmarandon.com/articles/python_mock_gotchas/ and lots of stackoverflow-questions. But I couldn't find a solution yet.

I try to mock two function, which are creating a database connection and put the data into a pandas dataFrame. They are used in the post-function (overwrites the django post-function):

def post(self, request, *args, **kwargs):
    db_connection = du_db.get_connection(dbtype='mssql', username=crd.MS_SQL_USER, password=crd.MS_SQL_PWD)
    df = du_db.fetch_dataframe(sql, connection=db_connection)

In the testing environment, get_connection should return nothing and fetch_dataframe should return a pandas dataframe defined before.

My testing class looks like this:

class IndexViewTest(TestCase):

@mock.patch('du_db.db.get_connection')
@mock.patch('du_db.db.fetch_dataframe')
def setUp(self, mock_get_connection, mock_fetch_dataframe):
    self.c = Client()
    mock_get_connection = mock_get_connection()
    mock_fetch_dataframe = mock_fetch_dataframe()
    mock_get_connection.return_value = ""
    df = {'lot_of_data': ['xy', 'z'], 'more_data': [8, 9]}
    mock_fetch_dataframe.return_value = pd.DataFrame(df)
    assert mock_get_connection is data_utils.db.get_connection()
    assert mock_fetch_dataframe is data_utils.db.fetch_dataframe()
    assert mock_get_connection.called
    assert mock_get_connection.called

# Lot of test-functions similar to this:
def test_valid_data(self):
    resp = self.c.post('/', data={'id': 3338})
    self.assertEqual(resp.status_code, 200)
    self.assertContains(resp, 'Hello', status_code=200)

I get the following error-message:

enter image description here

The replacing of the original functions through the mocks doesn't seam to work.

Thanks for your help.

ediordna
  • 300
  • 2
  • 15

1 Answers1

2
class IndexViewTest(TestCase):

    @mock.patch('du_db.db.get_connection')
    @mock.patch('du_db.db.fetch_dataframe')
    def setUp(self, mock_fetch_dataframe, mock_get_connection):
        self.c = Client()
        mock_get_connection = mock_get_connection() # remove this
        mock_fetch_dataframe = mock_fetch_dataframe() # remove this

When you call the mocks above, they return another new mock. By assigning to the same name, you are loosing the references to the patched mocks. You will be unable to configure or inspect them.

        mock_get_connection.return_value = "" # this is NOT the mock you think
                                              # unless removing lines above.
                                              # And so on...
        df = {'lot_of_data': ['xy', 'z'], 'more_data': [8, 9]}
        mock_fetch_dataframe.return_value = pd.DataFrame(df)

        # data__utils or du_db ??
        assert mock_get_connection is data_utils.db.get_connection()                                                                 
        assert mock_fetch_dataframe is data_utils.db.fetch_dataframe()
        assert mock_get_connection.called
        assert mock_get_connection.called

    # Lot of test-functions similar to this:
    def test_valid_data(self):
        resp = self.c.post('/', data={'id': 3338})
        self.assertEqual(resp.status_code, 200)
        self.assertContains(resp, 'Hello', status_code=200)

Edit: From what I understand, you already see the mocks at work from pdb. To make the tests work as you like, you need to patch every test function that uses those two functions for example with patch decorators. Then you have to setup the mocks inside the patched test functions. Also usually you assert in the tests but not in the setUp. I understand you made it here just for convenience as you were with some doubts about the running code.

If you were thinking in setting up the mocks in setUp for other test functions to use (you can't do it just like that, you would have to store them in self and then manage them again in the test functions), maybe you are interested in this example from Mock documentation:

An alternative way of managing patches is to use the patch methods: start and stop. These allow you to move the patching into your setUp and tearDown methods.

>>> class MyTest(TestCase):
    ...
    def setUp(self):
        ...
        self.patcher = patch(’mymodule.foo’)
        ...
        self.mock_foo = self.patcher.start()
    ...
    ...
    def test_foo(self):
        ...
        self.assertTrue(mymodule.foo is self.mock_foo)
        ...
        ...
    def tearDown(self):
        ...
        self.patcher.stop()
        ...
>>> MyTest(’test_foo’).run()
progmatico
  • 4,714
  • 1
  • 16
  • 27
  • Unfortunatelly i still get the same error after removing the two lines, the assertion i changed to du_db. If i set pdb and look for the `mock_get_connection ` i get: . And the other way arround with fetch_dataframe. I thought the connection is made through the order of @mock.patch – ediordna Jan 10 '19 at 08:20
  • 1
    @ediordna, functions receive the inner patches first. Haven't noticed the order was wrong (now edited). – progmatico Jan 10 '19 at 14:29
  • 1
    @ediordna See my 2nd edit. I hope this can help you in moving the mock setup into setUp – progmatico Jan 10 '19 at 16:27
  • Hi progmatico, thank you very much for your helpful answer. I'm still not really understanding the logic behind the whole python mocking, but I found a way to successfully mock the two functions in setUp(): `data_utils.db.get_connection = MagicMock(return_value="")` and `data_utils.db.fetch_dataframe = MagicMock(return_value = pd.DataFrame(df))`. – ediordna Jan 11 '19 at 14:38
  • But now I'm trying to mock a class, which is called as well in the post() function: In **post()**: `sf = Salesforce(username=NAME, password=PASSWORD, security_token=TOKEN)` I tried to mock the variable using: `self.mock_salesforce = mock.patch.object(battery_upgrade_web.views, 'sf', return_value="")` in setUp(). I also tried the patch() - way, but couldn't find a way to mock this. Maybe you could help me again? – ediordna Jan 11 '19 at 14:44
  • You just "monkey-patched" the two functions. It is more or less the same that patch decorator does, but without changing the testing function parameters. – progmatico Jan 11 '19 at 17:18
  • You should definitely read the many examples in the official unittest.mock documentation. Some things are simple with Mocks, but some are not and it takes some time to digest the thing. I suspect you are going fast paced. Take your time and test very small examples (even detached from your problem, because its simpler) until you understand what happens. – progmatico Jan 11 '19 at 17:19
  • The most common problem is that you patch the wrong "path" or you think you are using a mock and you aren't. We can't debug conversation, you have to show some more of the code. Either edit the question or post another snippet in a new one. I can't see why it doesn't work, it looks not many different to other cases, but I can't test it either, not even by similarity. Could also be a simple error. – progmatico Jan 11 '19 at 17:19
  • Hi progmatico, thanks for your support and good recommendations. Yes, I'm going fast paced. Propably your right and I should start with simpler examples to understand the basic concepts. – ediordna Jan 24 '19 at 10:54
  • I found a way to also mock the Salesforce connection. Not directly, but by mocking the __init__: `simple_salesforce.api.Salesforce.__init__ = MagicMock(return_value=None)`. – ediordna Jan 24 '19 at 10:58
  • 1
    @ediordna "... I also tried the patch() - way, but couldn't find a way to mock this. ". Wasn't that because the patch string was missing the package or module part? But even then you can assign a Mock directly. See [here](https://stackoverflow.com/questions/54137738/python-3-unittest-patch-doesnt-return-desired-value). Mocking init is strange because the object will exist (init is not a constructor) but you removed initialization of its attributes, and this is also not completely guaranteed, because some of the object attributes may still be assigned outside init. – progmatico Jan 26 '19 at 14:22