Merge lp:~billy-olsen/charms/trusty/mongodb/sample-unittest into lp:~mariosplivalo/charms/trusty/mongodb/replsets-fix-try

Proposed by Billy Olsen
Status: Merged
Merged at revision: 82
Proposed branch: lp:~billy-olsen/charms/trusty/mongodb/sample-unittest
Merge into: lp:~mariosplivalo/charms/trusty/mongodb/replsets-fix-try
Diff against target: 207 lines (+188/-0)
4 files modified
setup.cfg (+6/-0)
unit_tests/__init__.py (+2/-0)
unit_tests/test_hooks.py (+69/-0)
unit_tests/test_utils.py (+111/-0)
To merge this branch: bzr merge lp:~billy-olsen/charms/trusty/mongodb/sample-unittest
Reviewer Review Type Date Requested Status
Mario Splivalo Approve
Review via email: mp+244661@code.launchpad.net

Description of the change

Sample test case for testing one branch of the replicaset-relation-joined hook.

Includes comments for clarity (hopefully).

To post a comment you must log in.
Revision history for this message
Mario Splivalo (mariosplivalo) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added file 'setup.cfg'
2--- setup.cfg 1970-01-01 00:00:00 +0000
3+++ setup.cfg 2014-12-13 00:22:04 +0000
4@@ -0,0 +1,6 @@
5+[nosetests]
6+verbosity=2
7+with-coverage=1
8+cover-erase=1
9+cover-package=hooks
10+
11
12=== added file 'unit_tests/__init__.py'
13--- unit_tests/__init__.py 1970-01-01 00:00:00 +0000
14+++ unit_tests/__init__.py 2014-12-13 00:22:04 +0000
15@@ -0,0 +1,2 @@
16+import sys
17+sys.path.append('hooks')
18
19=== added file 'unit_tests/test_hooks.py'
20--- unit_tests/test_hooks.py 1970-01-01 00:00:00 +0000
21+++ unit_tests/test_hooks.py 2014-12-13 00:22:04 +0000
22@@ -0,0 +1,69 @@
23+from mock import patch
24+
25+import hooks
26+
27+from test_utils import CharmTestCase
28+
29+# Defines a set of functions to patch on the hooks object. Any of these
30+# methods will be patched by default on the default invocations of the
31+# hooks.some_func(). Invoking the the interface change relations will cause
32+# the hooks context to be created outside of the normal mockery.
33+TO_PATCH = [
34+ 'relation_id',
35+ 'relation_get',
36+ 'relation_set',
37+ 'unit_get',
38+ 'juju_log',
39+ 'config',
40+]
41+
42+
43+class MongoHooksTest(CharmTestCase):
44+
45+ def setUp(self):
46+ super(MongoHooksTest, self).setUp(hooks, TO_PATCH)
47+
48+ # The self.config object can be used for direct invocations of the
49+ # hooks methods. The side_effect of invoking the config object within
50+ # the hooks object will return the value that is set in the test case's
51+ # test_config dictionary
52+ self.config.side_effect = self.test_config.get
53+
54+ # Note: if we need to mock a specific class of an object, we need to
55+ # invoke the patch.object rather than simply mocking the module itself.
56+ # This is typically recommended for patching any object which belongs
57+ # to the object under test (lookup partial-mocks for more information).
58+ @patch.object(hooks, 'restart_mongod')
59+ @patch.object(hooks, 'enable_replset')
60+ # Note: patching the os.environ dictionary in-line here so there's no
61+ # additional parameter sent into the function
62+ @patch.dict('os.environ', JUJU_UNIT_NAME='fake-unit/0')
63+ def test_replica_set_relation_joined(self, mock_enable, mock_restart):
64+ # only have 1 invocation of the unit_get, so we'll just tell it what
65+ # to return. Can change to be more sophisticated when necessary.
66+ self.unit_get.return_value = 'private.address'
67+ self.test_config.set('port', '1234')
68+ self.test_config.set('replicaset', 'fake-replicaset')
69+ self.relation_id.return_value = 'fake-relation-id'
70+
71+ # Partial mock to control the flow around the if check for the enable
72+ # restart.
73+ mock_enable.return_value = False
74+
75+ # This tests the trigger of firing the relation-joined as the python
76+ # invocation works. However, it doesn't seem to accept all the mocking
77+ # in this case, so I'll just invoke the function directly.
78+ # hooks.hooks.execute(['hooks/replicaset-relation-joined'])
79+ hooks.replica_set_relation_joined()
80+
81+ # Verify that mongodb was NOT restarted since the replicaset we claimed
82+ # was not enabled.
83+ self.assertFalse(mock_restart.called)
84+
85+ exp_rel_vals = {'hostname': 'private.address',
86+ 'port': '1234',
87+ 'replset': 'fake-replicaset',
88+ 'install-order': '0',
89+ 'type': 'replset'}
90+ # Check that the relation data was set as we expect it to be set.
91+ self.relation_set.assert_called_with('fake-relation-id', exp_rel_vals)
92
93=== added file 'unit_tests/test_utils.py'
94--- unit_tests/test_utils.py 1970-01-01 00:00:00 +0000
95+++ unit_tests/test_utils.py 2014-12-13 00:22:04 +0000
96@@ -0,0 +1,111 @@
97+import logging
98+import unittest
99+import os
100+import yaml
101+import io
102+
103+from contextlib import contextmanager
104+from mock import patch
105+
106+
107+@contextmanager
108+def mock_open(filename, contents=None):
109+ ''' Slightly simpler mock of open to return contents for filename '''
110+ def mock_file(*args):
111+ if args[0] == filename:
112+ return io.StringIO(contents)
113+ else:
114+ return open(*args)
115+ with patch('__builtin__.open', mock_file):
116+ yield
117+
118+
119+def load_config():
120+ '''
121+ Walk backwords from __file__ looking for config.yaml, load and return the
122+ 'options' section'
123+ '''
124+ config = None
125+ f = __file__
126+ while config is None:
127+ d = os.path.dirname(f)
128+ if os.path.isfile(os.path.join(d, 'config.yaml')):
129+ config = os.path.join(d, 'config.yaml')
130+ break
131+ f = d
132+
133+ if not config:
134+ logging.error('Could not find config.yaml in any parent directory '
135+ 'of %s. ' % file)
136+ raise Exception
137+
138+ return yaml.safe_load(open(config).read())['options']
139+
140+
141+def get_default_config():
142+ '''
143+ Load default charm config from config.yaml return as a dict.
144+ If no default is set in config.yaml, its value is None.
145+ '''
146+ default_config = {}
147+ config = load_config()
148+ for k, v in config.iteritems():
149+ if 'default' in v:
150+ default_config[k] = v['default']
151+ else:
152+ default_config[k] = None
153+ return default_config
154+
155+
156+class CharmTestCase(unittest.TestCase):
157+ def setUp(self, obj, patches):
158+ super(CharmTestCase, self).setUp()
159+ self.patches = patches
160+ self.obj = obj
161+ self.test_config = TestConfig()
162+ self.test_relation = TestRelation()
163+ self.patch_all()
164+
165+ def patch(self, method):
166+ _m = patch.object(self.obj, method)
167+ mock = _m.start()
168+ self.addCleanup(_m.stop)
169+ return mock
170+
171+ def patch_all(self):
172+ for method in self.patches:
173+ setattr(self, method, self.patch(method))
174+
175+
176+class TestConfig(object):
177+ def __init__(self):
178+ self.config = get_default_config()
179+
180+ def get(self, attr):
181+ try:
182+ return self.config[attr]
183+ except KeyError:
184+ return None
185+
186+ def get_all(self):
187+ return self.config
188+
189+ def set(self, attr, value):
190+ if attr not in self.config:
191+ raise KeyError
192+ self.config[attr] = value
193+
194+
195+class TestRelation(object):
196+ def __init__(self, relation_data={}):
197+ self.relation_data = relation_data
198+
199+ def set(self, relation_data):
200+ self.relation_data = relation_data
201+
202+ def get(self, attr=None, unit=None, rid=None):
203+ if attr is None:
204+ return self.relation_data
205+ elif attr in self.relation_data:
206+ return self.relation_data[attr]
207+ return None

Subscribers

People subscribed via source and target branches

to all changes: