How to mock a SendGrid method in Python How to mock a SendGrid method in Python flask flask

How to mock a SendGrid method in Python


Explanation

Mocking has to be implemented with respect to where you are testing, and not where you have implemented the method. Or, also in your case, mocking the sg object from unittest will not work.

So, I am not exactly sure what the structure of your project is. But hopefully this example helps.

You need to make sure that you are also referencing the appropriate location of where that class is that you want to mock out, to properly mock out its methods.

Solution

So, let us assume you are running your tests from test.py:

test.py    your_app/        views.py    tests/        all_your_tests.py

Inside views.py, you are importing send like this:

from module_holding_your_class import SendGridClient

So, to look at your mock.patch, it should look like this:

@mock.patch('your_app.views.SendGridClient.send')def test_add_user_page_loads(self, mocked_send):

As you can see, you are running from test.py, so your imports are with reference from there. This is where I suggest running your tests with respect to where you actually run your real code, so that you don't have to mess around with your imports.

Furthermore, you are mocking the send that you are calling in views.py.

That should work. Let me know how that goes.

Extra Info: Mocking instance of a Class

So, based on your code, it would probably be more beneficial for you if you actually mocked out an instance of your class. This way you can very easily test all your methods within that single mock of the instance of SendGridClient, or even Mail. This way you can focus on the explicit behaviour of your method without worrying about functionality from externals.

To accomplish mocking out an instance of a Class (or in your case two), you will have to do something like this (explanation inline)

*This specific example is untested and probably not complete. The goal is to get you to understand how to manipulate the mock and the data to help your testing.

Further down below I have a fully tested example to play around with.*

@mock.patch('your_app.views.Mail')@mock.patch('your_app.views.SendGridClient')def test_add_user_page_loads(self, m_sendgridclient, m_mail):    # get an instance of Mock()    mock_sgc_obj = mock.Mock()    mock_mail_obj = mock.Mock()    # the return of your mocked SendGridClient will now be a Mock()    m_sendgridclient.return_value = mock_sgc_obj    # the return of your mocked Mail will now be a Mock()    m_mail.return_value = mock_mail_obj    # Make your actual call    resp = self.client.post('/add_user', data={            'email': 'joe@hotmail.com'        }, follow_redirects=True)    # perform all your tests    # example    self.assertEqual(mock_sgc_obj.send.call_count, 1)    # make sure that send was also called with an instance of Mail.    mock_sgc_obj.assert_called_once_with(mock_mail_obj)

Based on the code that you provided, I am not sure exactly what Mail is returning. I am assuming it is an object of Mail. If that is the case, then the above test case would suffice. However, if you are looking to test the content of message itself and make sure the data inside each of those object properties is correct, I strongly recommend separating your unittests to handle it in the Mail class and ensure that the data is behaving as expected.

The idea is that your add_user method should not care about validating that data yet. Just that a call was made with the object.

Furthermore, inside your send method itself, you can further unittest in there to make sure that the data you are inputting to the method is treated accordingly. This would make your life much easier.

Example

Here is a example I put together that I tested that I hope will help clarify this further. You can copy paste this in to your editor and run it. Pay attention to my use of __main__, it is to indicate where I am mocking from. In this case it is __main__.

Also, I would play around with side_effect and return_value (look at my examples) to see the different behaviour between the two. side_effect will return something that gets executed. In your case you are wanting to see what happens when you execute the method send.

Each unittest is mocking in different ways and showcasing the different use cases you can apply.

import unittestfrom unittest import mockclass Doo(object):    def __init__(self, stuff="", other_stuff=""):        passclass Boo(object):    def d(self):        return 'the d'    def e(self):        return 'the e'class Foo(object):    data = "some data"    other_data = "other data"    def t(self):        b = Boo()        res = b.d()        b.e()        return res    def do_it(self):        s = Stuff('winner')        s.did_it(s)    def make_a_doo(self):        Doo(stuff=self.data, other_stuff=self.other_data)class Stuff(object):    def __init__(self, winner):        self.winner = winner    def did_it(self, a_var):        return 'a_var'class TestIt(unittest.TestCase):    def setUp(self):        self.f = Foo()    @mock.patch('__main__.Boo.d')    def test_it(self, m_d):        '''            note in this test, one of the methods is not mocked.        '''        #m_d.return_value = "bob"        m_d.side_effect = lambda: "bob"        res = self.f.t()        self.assertEqual(res, "bob")    @mock.patch('__main__.Boo')    def test_them(self, m_boo):        mock_boo_obj = mock.Mock()        m_boo.return_value = mock_boo_obj        self.f.t()        self.assertEqual(mock_boo_obj.d.call_count, 1)        self.assertEqual(mock_boo_obj.e.call_count, 1)    @mock.patch('__main__.Stuff')    def test_them_again(self, m_stuff):        mock_stuff_obj = mock.Mock()        m_stuff.return_value = mock_stuff_obj        self.f.do_it()        mock_stuff_obj.did_it.assert_called_once_with(mock_stuff_obj)        self.assertEqual(mock_stuff_obj.did_it.call_count, 1)    @mock.patch('__main__.Doo')    def test_them(self, m_doo):        self.f.data = "fake_data"        self.f.other_data = "some_other_fake_data"        self.f.make_a_doo()        m_doo.assert_called_once_with(            stuff="fake_data", other_stuff="some_other_fake_data"        )if __name__ == '__main__':    unittest.main()