0

I have two following files:

testcase_module.py

import boto3


ec2 = boto3.resource('ec2')


def f():
    return ec2.instances.all()

testcase_test.py

import testcase_module
import unittest.mock


class MainTest(unittest.TestCase):
    @unittest.mock.patch('testcase_module.ec2', spec_set=['instances'])
    def test_f(self, ec2_mock):
        ec2_mock.instances.spec_set = ['all']
        testcase_module.f()


if __name__ == '__main__':
    unittest.main()

I added spec_test parameter to the patch because I would like to assert if any other function than instances.all() has been called, but changing string 'all' to 'allx' doesn't make test fail while changing 'instances' to 'instancesx' does. I tried the following changes (git diff testcase_test.py and python testcase_test.py results below):

Attempt 1:

diff --git a/testcase_test.py b/testcase_test.py
index d6d6e59..ae274c8 100644
--- a/testcase_test.py
+++ b/testcase_test.py
@@ -3,9 +3,8 @@ import unittest.mock


 class MainTest(unittest.TestCase):
-    @unittest.mock.patch('testcase_module.ec2', spec_set=['instances'])
-    def test_f(self, ec2_mock):
-        ec2_mock.instances.spec_set = ['all']
+    @unittest.mock.patch('testcase_module.ec2', spec_set=['instances.all'])
+    def test_f(self, _):
         testcase_module.f()

Produces:

E
======================================================================
ERROR: test_f (__main__.MainTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/usr/lib/python3.5/unittest/mock.py", line 1157, in patched
    return func(*args, **keywargs)
  File "testcase_test.py", line 8, in test_f
    testcase_module.f()
  File "/path/to/project/testcase_module.py", line 8, in f
    return ec2.instances.all()
  File "/usr/lib/python3.5/unittest/mock.py", line 578, in __getattr__
    raise AttributeError("Mock object has no attribute %r" % name)
AttributeError: Mock object has no attribute 'instances'

----------------------------------------------------------------------
Ran 1 test in 0.001s

FAILED (errors=1)

Attempt 2:

diff --git a/testcase_test.py b/testcase_test.py
index d6d6e59..d93abd1 100644
--- a/testcase_test.py
+++ b/testcase_test.py
@@ -3,9 +3,8 @@ import unittest.mock


 class MainTest(unittest.TestCase):
-    @unittest.mock.patch('testcase_module.ec2', spec_set=['instances'])
-    def test_f(self, ec2_mock):
-        ec2_mock.instances.spec_set = ['all']
+    @unittest.mock.patch('testcase_module.ec2.instances', spec_set=['all'])
+    def test_f(self):
         testcase_module.f()

Produces:

E
======================================================================
ERROR: test_f (__main__.MainTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/usr/lib/python3.5/unittest/mock.py", line 1149, in patched
    arg = patching.__enter__()
  File "/usr/lib/python3.5/unittest/mock.py", line 1312, in __enter__
    setattr(self.target, self.attribute, new_attr)
AttributeError: can't set attribute

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/usr/lib/python3.5/unittest/mock.py", line 1170, in patched
    patching.__exit__(*exc_info)
  File "/usr/lib/python3.5/unittest/mock.py", line 1334, in __exit__
    delattr(self.target, self.attribute)
AttributeError: can't delete attribute

----------------------------------------------------------------------
Ran 1 test in 0.001s

FAILED (errors=1)

How can I make it failing when other method than instances.all has been called?

pt12lol
  • 2,332
  • 1
  • 22
  • 48

2 Answers2

0

Try using mock_add_spec.

ec2_mock.instances.mock_add_spec(['all'], spec_set=True)

Link: https://docs.python.org/3/library/unittest.mock.html#unittest.mock.Mock.mock_add_spec

Luke Sneeringer
  • 9,270
  • 2
  • 35
  • 32
-1

What about doing it like this:

@unittest.mock.patch('testcase_module.boto3.resource', autospec=True)
def test_f(self, ec2_resource_mock):
    class InstanceStub(object):
        def all(self):
            return [...]
    ec2_resource_mock.return_value = mock.create_autospec(
        EC2InstanceType, instances=InstanceStub())
    testcase_module.f()
Dan
  • 1,874
  • 1
  • 16
  • 21
  • What is `EC2InstanceType`? – pt12lol Jun 29 '17 at 07:49
  • And why it is so important to mock `resource` function instead of `ec2` object? I am asking, because you are not the first person suggesting this approach, but I am still not able to understand the difference in practice. Mocking `resource` seems to be a bit more complicated. – pt12lol Jun 29 '17 at 07:53
  • That's the easiest way since you're really interested in constraining the return value of resource. – Dan Jun 29 '17 at 16:59
  • This is not an answer for the question 'why'. Mocking ec2 object sounds easier for me. Why mocking resource is easier for you? – pt12lol Jun 29 '17 at 17:11
  • It's because ec2 is just a name, the return value of reference call is what you are really interested in. – Dan Jun 29 '17 at 17:52
  • By 'ec2' I mean an object returned by 'resource' function call, as is in example in my question. – pt12lol Jun 29 '17 at 17:58
  • What is `EC2InstanceType`? – pt12lol Jun 29 '17 at 17:59
  • It is the class name of the object returned from resource call. I couldn't find it in the docs but you can find it by logging `type(ec2)`. – Dan Jun 29 '17 at 20:08