Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
403 views
in Technique[技术] by (71.8m points)

python - Mock class instances without calling `__init__` and mock their respective attributes

I have a class MyClass with a complex __init__ function.

This class had a method my_method(self) which I would like to test.

my_method only needs attribute my_attribute from the class instance.

Is there a way I can mock class instances without calling __init__ and by setting the attributes of each class instance instead?

What I have:

# my_class.py

from utils import do_something

class MyClass(object):

   def __init__(self, *args, **kwargs):
       # complicated function which I would like to bypass when initiating a mocked instance class
       pass

   def my_method(self):
      return do_something(self.my_attribute)

What I tried

@mock.patch("my_class.MyClass")
def test_my_method(class_mock, attribute):
   
   instance = class_mock.return_value
   instance.my_attribute = attribute

   example_instance = my_class.MyClass()

   out_my_method = example_instance.my_method()
   # then perform some assertions on `out_my_method`

however this still makes usage of __init__ which I hope we can by-pass or mock.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

As I mentioned in the comments, one way to test a single method without having to create an instance is:

MyClass.my_method(any_object_with_my_attribute)

The problem with this, as with both options in quamrana's answer, is that we have now expanded the scope of any future change just because of the tests. If a change to my_method requires access to an additional attribute, we now have to change both the implementation and something else (the SuperClass, the MockMyClass, or in this case any_object_with_my_attribute_and_another_one).


Let's have a more concrete example:

import json


class MyClass:

    def __init__(self, filename):
        with open(filename) as f:
            data = json.load(f)
        self.foo = data.foo
        self.bar = data.bar
        self.baz = data.baz

    def my_method(self):
        return self.foo ** 2

Here any test that requires an instance of MyClass. is painful because of the file access in __init__. A more testable implementation would split apart the detail of how the data is accessed and the initialisation of a valid instance:

class MyClass:
    
    def __init__(self, foo, bar, baz):
        self.foo = foo
        self.bar = bar
        self.baz = baz

    def my_method(self):
        return self.foo ** 2

    @classmethod
    def from_json(cls, filename):
        with open(filename) as f:
            data = json.load(f)
        return cls(data.foo, data.bar, data.baz)

You have to refactor MyClass("path/to/file") to MyClass.from_json("path/to/file"), but wherever you already have the data (e.g. in your tests) you can use e.g. MyClass(1, 2, 3) to create the instance without requiring a file (you only need to consider the file in the tests of from_json itself). This makes it clearer what the instance actually needs, and allows the introduction of other ways to construct an instance without changing the interface.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...