Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Mocking Method Calls In Python

I have been searching stack exchange and around the web for how to do this, but I cannot understand how to mock behaviors for methods. I am trying to mock openpyxl behaviors and behaviors for my custom class. Here is my attempt:

import unittest
from unittest.mock import MagicMock
import openpyxl 
from MyPythonFile import MyClass

class TestMyClass(unittest.TestCase):
  def test_myclass(self):
    myclass = MyClass()
    wb = openpyxl.workbook()
    ws = openpyxl.worksheet()
    wbPath = 'wbPath'

    openpyxl.load_workbook(wbPath, data_only = True) = MagicMock(return_value=wb)

When I try the final line I get the error "can't assign to function call". Do I need to use patch.object('openpyxl','load_workbook')? I am used to mocking in Java with Groovy and it's pretty straightforward.

*Edit: wanted to add the finalized version of the test based on the response from @alxwrd

import unittest
from unittest.mock import MagicMock
import openpyxl 
import configparser
from MyPythonFile import MyClass

class TestMyClass(unittest.TestCase):
  def test_myclass(self):
    myclass = MyClass()
    wb = openpyxl.workbook()
    ws = openpyxl.worksheet()
    config = configparser.ConfigParser()

    openpyxl.load_workbook = MagicMock(return_value=wb)
    wb.get_sheet_by_name = MagicMock(return_value=ws)

    config.sections() = MagicMock(return_value=['Section1'])
    config.get = MagicMock(side_effect=['Value1','Value2']) 

Notice that config.get gives multiple returns with the side_effect parameter, so if config.get() is called once in the code it returns 'Value1' and when config.get() is called a second time it returns 'Value2'.

like image 754
EliSquared Avatar asked May 12 '17 15:05

EliSquared


People also ask

What is mocking in Python?

mock is a library for testing in Python. It allows you to replace parts of your system under test with mock objects and make assertions about how they have been used. unittest. mock provides a core Mock class removing the need to create a host of stubs throughout your test suite.

What is mocking and patching in Python?

mock provides a powerful mechanism for mocking objects, called patch() , which looks up an object in a given module and replaces that object with a Mock . Usually, you use patch() as a decorator or a context manager to provide a scope in which you will mock the target object.

What is the difference between mock and MagicMock?

Mock vs. So what is the difference between them? MagicMock is a subclass of Mock . It contains all magic methods pre-created and ready to use (e.g. __str__ , __len__ , etc.). Therefore, you should use MagicMock when you need magic methods, and Mock if you don't need them.


2 Answers

You can't override a function call, but you can override the function itself.

From the docs:

>>> from unittest.mock import MagicMock
>>> thing = ProductionClass()
>>> thing.method = MagicMock(return_value=3)
>>> thing.method(3, 4, 5, key='value')
3
>>> thing.method.assert_called_with(3, 4, 5, key='value')

So in your case:

openpyxl.load_workbook = MagicMock(return_value=wb)
like image 129
alxwrd Avatar answered Sep 18 '22 15:09

alxwrd


You don't have to import the target you want to mock in your unit tests. Use patch to mock the target. Let's assume your code has this import statement: import openpyxl. Patch then can be used in your test as a decorator:

import unittest
from unittest import mock
from MyPythonFile import MyClass

class TestMyClass(unittest.TestCase):

    @mock.patch('MyPythonFile.openpyxl')
    def test_myclass(self, openpyxl_mock):
        wb_dummy = 'foo'
        openpyxl_mock.load_workbook.return_value = wb_dummy

        myclass = MyClass()
        myclass.load_workbook()  # assuming this calls openpyxl.load_workbook()

Note that you have to add an argument to the test method which will get the mock object.

Or as a context manager:

import unittest
from unittest import mock
from MyPythonFile import MyClass

class TestMyClass(unittest.TestCase):

    def test_myclass(self):
        with mock.patch('MyPythonFile.openpyxl') as openpyxl_mock:
            wb_dummy = 'foo'
            openpyxl_mock.load_workbook.return_value = wb_dummy

            myclass = MyClass()
            myclass.load_workbook()  # assuming this calls openpyxl.load_workbook()
like image 26
Milo Avatar answered Sep 18 '22 15:09

Milo