How to make fun of the SendGrid method in Python

I am trying to make fun of the SendGrid method in my Flask view function so that it does not send an email during testing. When I run the code below, I get the error message "ImportError: There is no module named sg". How can I properly configure the 'sg' method so that it can be found during testing?

# test_helpers.py from unittest import TestCase from views import app class PhotogTestCase(TestCase): def setUp(self): app.config['WTF_CSRF_ENABLED'] = False app.config['TESTING'] = True self.app = app self.client = app.test_client() # test_views.py import mock from test_helpers import PhotogTestCase import sendgrid class TestAddUser(PhotogTestCase): sg = sendgrid.SendGridClient(app.config['SENDGRID_API_KEY']) @mock.patch('sg.send') def test_add_user_page_loads(self, mocked_send): mocked_send.return_value = None # Do nothing on send resp = self.client.post('/add_user', data={ 'email': ' joe@hotmail.com ' }, follow_redirects=True) assert 'Wow' in resp.data # views.py import sendgrid from itsdangerous import URLSafeTimedSerializer from flask import Flask, redirect, render_template, \ request, url_for, flash, current_app, abort from flask.ext.stormpath import login_required from forms import RegistrationForm, AddContactForm, \ AddUserForm @app.route('/add_user', methods=['GET', 'POST']) @login_required def add_user(): """ Send invite email with token to invited user """ form = AddUserForm() if form.validate_on_submit(): # token serializer ts = URLSafeTimedSerializer(app.config['SECRET_KEY']) email = request.form['email'] tenant_id = user.custom_data['tenant_id'] # create token containing email and tenant_id token = ts.dumps([email, tenant_id]) # create url with token, eg /add_user_confirm/asdf-asd-fasdf confirm_url = url_for( 'add_user_confirm', token=token, _external=True) try: # sendgrid setup sg = sendgrid.SendGridClient( app.config['SENDGRID_API_KEY'], raise_errors=True ) # email setup message = sendgrid.Mail( to=request.form['email'], subject='Account Invitation', html='You have been invited to set up an account on PhotogApp. Click here: ' + confirm_url, from_email=' support@photogapp.com ' ) # send email status, msg = sg.send(message) flash('Invite sent successfully.') return render_template('dashboard/add_user_complete.html') return render_template('dashboard/add_user.html', form=form) 
+5
source share
1 answer

Explanation

Failure should be performed in relation to where you are testing, and not where you implemented this method. Or, also in your case, mocking the st object from unittest will not work.

So, I'm not quite sure what the structure of your project is. But hopefully this example helps.

You need to make sure that you also refer to a suitable place where this class is the one you want to make fun of in order to properly mock its methods.

Decision

So let's say you use your tests from test.py:

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

Inside views.py, you import the submission as follows:

 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 working with test.py, so your import is with a link from there. Here I suggest you run your tests as to where you really run your real code so that you don't have to interfere with your import.

In addition, you are mocking the send that you call in views.py.

That should work. Let me know how this happens.

Additional Information: Mocking instance of class

So, based on your code, it would probably be more useful for you if you really mocked an instance of your class. This way, you can very easily test all of your methods within this single layout of your SendGridClient or even Mail instance. That way, you can focus on the explicit behavior of your method without worrying about external functionality.

To mock an instance of a class (or two in your case), you will need to do something like this (inline explanation)

* This specific example has not been verified and is probably not complete. The goal is to make you understand how to manipulate layout and data to help you in testing.

Further below, I have a fully tested example to play 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 you provided, I'm not sure what exactly Mail is returning. I assume this is a Mail object. If so, then the above test case will suffice. However, if you want to check the contents of the message itself and make sure that the data inside each of these object properties is correct, I highly recommend separating your unittests to process it in the Mail class, and make sure the data is as expected.

The idea is that your add_user method add_user not have to worry about validating this data. It’s just that the call was made with an object.

In addition, inside the sending method itself, you can continue the installation there to make sure that the data you enter into the method is processed accordingly. It will make your life easier.

Example

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

Also, I would play with side_effect and return_value (look at my examples) to see different behavior between them. side_effect will return something that will be executed. In your case, you want to see what happens when you complete the submit method.

Each unittest mocks in different ways and demonstrates different use cases that you can apply.

 import unittest from unittest import mock class Doo(object): def __init__(self, stuff="", other_stuff=""): pass class 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 = bd() be() 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.ft() 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.ft() 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() 
+6
source

Source: https://habr.com/ru/post/1234403/


All Articles