50

Let's say I have a class like this.

class SomeProductionProcess(CustomCachedSingleTon):
    
    @classmethod
    def loaddata(cls):
        """
        Uses an iterator over a large file in Production for the Data pipeline.
        """
        pass

Now at test time I want to change the logic inside the loaddata() method. It would be a simple custom logic that doesn't process large data.

How do we supply custom implementation of loaddata() at testtime using Python Mock UnitTest framework?

Brendan Abel
  • 35,343
  • 14
  • 88
  • 118
Vineel
  • 1,630
  • 5
  • 26
  • 47
  • 1
    please note that loaddata() is a @classmethod. Also I want to supply a custom loaddata() to be able to test another method in this class. – Vineel Jul 26 '16 at 02:44
  • 1
    'mock method' would be more correct given the question, since mock 'classmethod' is implied when you say 'class method'. A method is always a part of a class instance and a classmethod is part of the class. To make it correct change method with a decorator @classmethod and add parameter cls. – msudder Apr 12 '17 at 14:30

3 Answers3

71

Here is a simple way to do it using mock

import mock


def new_loaddata(cls, *args, **kwargs):
    # Your custom testing override
    return 1


def test_SomeProductionProcess():
    with mock.patch.object(SomeProductionProcess, 'loaddata', new=new_loaddata):
        obj = SomeProductionProcess()
        obj.loaddata()  # This will call your mock method

I'd recommend using pytest instead of the unittest module if you're able. It makes your test code a lot cleaner and reduces a lot of the boilerplate you get with unittest.TestCase-style tests.

Brendan Abel
  • 35,343
  • 14
  • 88
  • 118
  • 1
    Thanks , so the method you mentioned, is it according to unittest or pytest? – Vineel Jul 26 '16 at 02:25
  • 2
    `mock` can be used with either. The difference between using `pytest` or `unittest` just changes what your test functions look like. My example uses the `pytest` format, where you just use regular functions and the built in `assert` statement. Take a look at @willnx [answer](http://stackoverflow.com/a/38579804/1547004) for an example of what a `unittest` test looks like. – Brendan Abel Jul 26 '16 at 05:25
  • `mock` is now part of the Python standard library, available as `unittest.mock` in Python 3.3 onwards – slavugan Apr 21 '22 at 12:01
  • @hello Thanks for the comment, but If it's helpful, an upvote is all that is needed. – Brendan Abel Jul 26 '22 at 04:19
19

To easily mock out a class method with a structured return_value, can use unittest.mock.Mock.

from unittest.mock import Mock

mockObject = SomeProductionProcess
mockObject.loaddata = Mock(return_value=True)

EDIT:

Since you want to mock out the method with a custom implementation, you could just create a custom mock method object and swap out the original method at testing runtime.

def custom_method(*args, **kwargs):
    # do custom implementation

SomeProductionProcess.loaddata = custom_method
Mattew Whitt
  • 2,194
  • 1
  • 15
  • 19
7

Lets say you have a module named awesome.py and in it, you had:

import time

class SomeProductionProcess(CustomCachedSingleTon):

    def loaddata(self):
        time.sleep(30) # simulating a long running process
        return 2

Then your unittest where you mock loaddata could look like this:

import unittest

import awesome # your application module


class TestSomeProductionProcess(unittest.TestCase):
    """Example of direct monkey patching"""

    def test_loaddata(self):
        some_prod_proc = awesome.SomeProductionProcess()
        some_prod_proc.loaddata = lambda x: 2 # will return 2 every time called
        output = some_prod_proc.loaddata()
        expected = 2

        self.assertEqual(output, expected)

Or it could look like this:

import unittest
from mock import patch

import awesome # your application module

class TestSomeProductionProcess(unittest.TestCase):
    """Example of using the mock.patch function"""

    @patch.object(awesome.SomeProductionProcess, 'loaddata')
    def test_loaddata(self, fake_loaddata):
        fake_loaddata.return_value = 2
        some_prod_proc = awesome.SomeProductionProcess()

        output = some_prod_proc.loaddata()
        expected = 2

        self.assertEqual(output, expected)

Now when you run your test, loaddata wont take 30 seconds for those test cases.

willnx
  • 1,253
  • 1
  • 8
  • 14
  • Thanks a lot for your response. Instead of return 2, I want to provide a stub that needs to be implemented , how would I do it ? instead of lamdba x:2 – Vineel Jul 26 '16 at 02:57
  • Whatever you put on the right side of the `:` of the lambda will be returned. – willnx Jul 26 '16 at 14:54
  • How about me completely removing lambda expression? And just assigning it a custom function. Like this : some_prod_proc = custom_func(). And I m worried that it will completely override the function definition and not just in test environment. – Vineel Jul 26 '16 at 17:49
  • Yeah, bind it to a custom function just like you described! The cool thing with Python is that it's Dynamically Typed so you can monkey patch objects at run time (vs Java, which you cannot dynamically reassign methods at run time; this is why dependency injection is the 'go-to' solution for Java unittesting).As long as you don't modify the source and are just patching the object in your unittest, you don't have to worry about overriding them for production. – willnx Jul 27 '16 at 01:11