1

I'm using MagicMock to test a function in a web app. The function is imported directly from a module.

The expected behaviour is: when the function being tested is called, it calls on a third party api (but I'm mocking this for my test). That returns a dictionary and the function under test inserts that into an object and returns the object.

That works fine when I use nosetests to run that specific module of tests.

When I use nosetests to discover and run tests in my test/unit/ folder, the test doesn't work as expected. Instead the mocked API returns a NoneType and the function being tested returns a Magic Mock instance.

The test:

def test_get_user_facebook_data_1(self):
    facebook_oauth_response = {u'name': u'Jack Jacker', u'email': u'jack@jack.jack', u'id': u'sd5Jtvtk6'}
    facepy.GraphAPI.get = MagicMock(return_value=facebook_oauth_response)

    user_facebook_data = user_service.get_user_facebook_data('bogus_facebook_oauth_access_token')

    self.assertEquals(user_facebook_data._facebook_oauth_id, u'sd5Jtvtk6')
    self.assertEquals(user_facebook_data._email, u'jack@jack.jack')
    self.assertEquals(user_facebook_data._full_name, u'Jack Jacker')

The function being tested (in user_service module):

def get_user_facebook_data(facebook_access_token):

    '''
    With a user's FB access token, retrieve their credentials to either create a new account or login. Create a user object from the user model,         but don't save
    '''

    try:
        graph = facepy.GraphAPI(facebook_access_token)
        facebook_data = graph.get('me?fields=id,name,email')
    except facepy.exceptions.OAuthError:
        raise errors.FacebookAccessTokenInvalidError()

    user = user_model.User()

    try:
        facebook_oauth_id = facebook_data[u'id']
        user.set_new_fb_oauth(facebook_oauth_id)
    except KeyError:
        raise errors.OauthNoIdError()

    try:
        email = facebook_data[u'email']
        user.set_new_email(email)
    except KeyError:
        pass

    try:
        full_name = facebook_data[u'name']
        user.set_new_full_name(full_name)
    except KeyError:
        pass

    return user

Can you please help me understand why the result is inconsistent?

EDIT

New information - if I use nosetests on the module directly, the function I'm testing accesses the mocked Facepy dictionary values as unicode (as expected). If I user nosetests to discover tests, or if I use the solution posted by dm03514 below and run the tests directly, the function accesses the dictionary from the mocked facepy API as Magic Mock instances. Meaning, each result of accessing the dict is an Magic Mock instance.

That's confusing, as I set the return_value (in all tests) to be the dictionary.

JasTonAChair
  • 1,948
  • 1
  • 19
  • 31
  • Update answer :) the test is patching the `get` method on the class and NOT on the instance. The instance can be referenced using `return_value` – dm03514 Mar 06 '17 at 14:00

2 Answers2

2

Sorry long day, so can't really mentally parse through why things are working the way they currently are :p

But to solve it so it performs the same way regardless of where the test is executed, is to patch facepy import in the user_service module.

def test_get_user_facebook_data_1(self):
    facebook_oauth_response = {u'name': u'Jack Jacker', u'email': u'jack@jack.jack', u'id': u'sd5Jtvtk6'}
    with mock.patch('module.path.to.user_service.facepy') as mock_facepy:
      mock_facepy.GraphAPI.return_vaule.get = MagicMock(return_value=facebook_oauth_response)

      user_facebook_data = user_service.get_user_facebook_data('bogus_facebook_oauth_access_token')

      self.assertEquals(user_facebook_data._facebook_oauth_id, u'sd5Jtvtk6')
      self.assertEquals(user_facebook_data._email, u'jack@jack.jack')
      self.assertEquals(user_facebook_data._full_name, u'Jack Jacker')

The above patches the facepy local to user_service module.

dm03514
  • 54,664
  • 18
  • 108
  • 145
  • I did try this, so thanks. This looks like the smart way to mock things, but my issue persists (updated my question). – JasTonAChair Mar 06 '17 at 03:07
  • Sorry, updated, forgot return value, should work now. Since graph API is being instantiated mock needs to some how specifiythat you'd like to patch get on instances and not on the class. – dm03514 Mar 06 '17 at 11:45
  • So, thanks again. This did work when I run nosetests with this module specifically, but my issue with allowing nosetests to discover tests continues. Regardless, the info about `return_value` is very useful. – JasTonAChair Mar 06 '17 at 23:55
0

My issue was a misunderstanding about the way MagicMock handles dictionaries. You need to declare its __getitem__ property.

I think the "inconsistency" I mentioned was more of being a fluke that my tests worked at all.

This borrows heavily from @dm03514's answer.

def test_get_user_facebook_data_1(self):
    facebook_oauth_response = {u'name': u'Jack Jacker', u'email': u'jack@jack.jack', u'id': u'sd5Jtvtk6'}
    with mock.patch('api.services.user_service.facepy') as mock_facepy:

        # Mocking the response from the facepy.
        # Setting this side effect allows the Mock object to be accessed as a dict.
        def getitem(name):
            return facebook_oauth_response[name]
        mock_oauth = MagicMock()
        mock_oauth.return_value = facebook_oauth_response 
        mock_oauth.__getitem__.side_effect = getitem
        mock_facepy.GraphAPI.return_value.get = mock_oauth

        user_facebook_data = user_service.get_user_facebook_data('bogus_facebook_oauth_access_token')

        self.assertEquals(user_facebook_data._facebook_oauth_id, u'sd5Jtvtk6')
        self.assertEquals(user_facebook_data._email, u'jack@jack.jack')
        self.assertEquals(user_facebook_data._full_name, u'Jack Jacker')
Stevoisiak
  • 23,794
  • 27
  • 122
  • 225
JasTonAChair
  • 1,948
  • 1
  • 19
  • 31