30

I'm trying to make a simple test in python, but I'm not able to figure it out how to accomplish the mocking process.

This is the class and def code:

class FileRemoveOp(...)
    @apply_defaults
    def __init__(
            self,
            source_conn_keys,
            source_conn_id='conn_default',
            *args, **kwargs):
        super(v4FileRemoveOperator, self).__init__(*args, **kwargs)
        self.source_conn_keys = source_conn_keys
        self.source_conn_id = source_conn_id


    def execute (self, context)
          source_conn = Connection(conn_id)
          try:
              for source_conn_key in self.source_keys:
                  if not source_conn.check_for_key(source_conn_key):    
                      logging.info("The source key does not exist")  
                  source_conn.remove_file(source_conn_key,'')
          finally:
              logging.info("Remove operation successful.")

And this is my test for the execute function:

@mock.patch('main.Connection')
def test_remove_execute(self,MockConn):
    mock_coon = MockConn.return_value
    mock_coon.value = #I'm not sure what to put here#
    remove_operator = FileRemoveOp(...)
    remove_operator.execute(self)

Since the execute method try to make a connection, I need to mock that, I don't want to make a real connection, just return something mock. How can I make that? I'm used to do testing in Java but I never did on python..

flazzarini
  • 7,791
  • 5
  • 33
  • 34
AnaF
  • 323
  • 1
  • 3
  • 8
  • 1
    What are you actually trying to test? Usually you are mocking stuff that is called by the code you are testing and don't call the mock directly. With your approach you will test the mock only. – Klaus D. Aug 17 '16 at 19:22
  • A more complete example would be useful. It's hard to gauge what you're trying to achieve. That said, when the mocked class is called it will automatically create another mock as the returned instance. You can then setup this mocked connection as needed. – Dunes Aug 17 '16 at 19:24

2 Answers2

51

First it is very important to understand that you always need to Mock where it the thing you are trying to mock out is used as stated in the unittest.mock documentation.

The basic principle is that you patch where an object is looked up, which is not necessarily the same place as where it is defined.

Next what you would need to do is to return a MagicMock instance as return_value of the patched object. So to summarize this you would need to use the following sequence.

  • Patch Object
  • prepare MagicMock to be used
  • return the MagicMock we've just created as return_value

Here a quick example of a project.

connection.py (Class we would like to Mock)

class Connection(object):                                                        
    def execute(self):                                                           
        return "Connection to server made"

file.py (Where the Class is used)

from project.connection import Connection                                        


class FileRemoveOp(object):                                                      
    def __init__(self, foo):                                                     
        self.foo = foo                                                           

    def execute(self):                                                           
        conn = Connection()                                                      
        result = conn.execute()                                                  
        return result    

tests/test_file.py

import unittest                                                                  
from unittest.mock import patch, MagicMock                                       
from project.file import FileRemoveOp                                            

class TestFileRemoveOp(unittest.TestCase):                                       
    def setUp(self):                                                             
        self.fileremoveop = FileRemoveOp('foobar')                               

    @patch('project.file.Connection')                                            
    def test_execute(self, connection_mock):
        # Create a new MagickMock instance which will be the
        # `return_value` of our patched object                                     
        connection_instance = MagicMock()                                        
        connection_instance.execute.return_value = "testing"

        # Return the above created `connection_instance`                     
        connection_mock.return_value = connection_instance                       

        result = self.fileremoveop.execute()                                     
        expected = "testing"                                                     
        self.assertEqual(result, expected)                                       

    def test_not_mocked(self):
        # No mocking involved will execute the `Connection.execute` method                                                   
        result = self.fileremoveop.execute()                                     
        expected = "Connection to server made"                                   
        self.assertEqual(result, expected) 
flazzarini
  • 7,791
  • 5
  • 33
  • 34
  • 2
    Thanks a lot! This was a perfect example and works correctly! :) – AnaF Aug 17 '16 at 20:45
  • That's almost what I need, is it possible to, for example, patch `project.file.Connection` with a slightly different version (maybe `project.file.Connection2`) rather than patch with `MagicMock` ? – Rastikan May 29 '18 at 18:39
  • In that case I suggest you should have a look into [depdency injection](http://python-dependency-injector.ets-labs.org/introduction/di_in_python.html) – flazzarini May 31 '18 at 11:50
  • what is connection mock? Is it the last argument? What if you had two things to mock? – Jwan622 Jan 10 '19 at 19:01
  • If you want to add a second patch to the method you will have to use two `patch` decorators which can be accessed in the method signature in inverted order. Here is an [example](https://docs.python.org/3/library/unittest.mock.html#nesting-patch-decorators) – flazzarini Jan 15 '19 at 10:06
  • 4
    This example is gold – Kyle Bridenstine Sep 25 '19 at 13:06
  • I have say 20 connections. I created a mock class and substituted it by modifying import statement in test/__init__.py import sys sys.modules["connection"] = __import__("mock_connection", globals(), locals(), [], -1) In the mock class I have dictionary of mock objects and I can set return value for every object as I want. It works but I would rather use standard mock/patch things to do it, but I do not know how – uuu777 Mar 05 '20 at 15:51
  • @zzz777 Dependency injection might the solution you are looking for. Have a look at this detailed article which explains it more in depth https://preslav.me/2018/12/20/dependency-injection-in-python/ – flazzarini Sep 04 '20 at 05:17
  • What if the class to be mocked is not imported but just returned (an object instance) by some library method? – MattSom Jun 14 '21 at 15:17
  • In that case replace the function of the library with a `MagicMock` and set the `return_value` of that created MagicMock to your mocked Object – flazzarini Jun 15 '21 at 20:12
0

I found that this simple solution works in python3: you can substitute a whole class before it is being imported for the first time. Say I have to mock class 'Manager' from real.manager

class MockManager:
    ...

import real.manager
real.manager.Manager = MockManager

It is possible to do this substitution in init.py if there is no better place. It may work in python2 too but I did not check.

uuu777
  • 765
  • 4
  • 21