You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
160 lines
11 KiB
160 lines
11 KiB
<!DOCTYPE html> |
|
<html lang="en"> |
|
<head> |
|
<meta http-equiv="X-UA-Compatible" content="IE=edge"> |
|
<meta http-equiv="content-type" content="text/html; charset=utf-8"> |
|
|
|
<!-- Enable responsiveness on mobile devices--> |
|
<!-- viewport-fit=cover is to support iPhone X rounded corners and notch in landscape--> |
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1, viewport-fit=cover"> |
|
|
|
<title>Julio Biason .Me 4.3</title> |
|
|
|
<!-- CSS --> |
|
<link rel="stylesheet" href="https://blog.juliobiason.me/print.css" media="print"> |
|
<link rel="stylesheet" href="https://blog.juliobiason.me/poole.css"> |
|
<link rel="stylesheet" href="https://blog.juliobiason.me/hyde.css"> |
|
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=PT+Sans:400,400italic,700|Abril+Fatface"> |
|
|
|
|
|
|
|
|
|
|
|
</head> |
|
|
|
<body class=" "> |
|
|
|
<div class="sidebar"> |
|
<div class="container sidebar-sticky"> |
|
<div class="sidebar-about"> |
|
|
|
<a href="https://blog.juliobiason.me"><h1>Julio Biason .Me 4.3</h1></a> |
|
|
|
<p class="lead">Old school dev living in a 2.0 dev world</p> |
|
|
|
|
|
</div> |
|
|
|
<ul class="sidebar-nav"> |
|
|
|
|
|
<li class="sidebar-nav-item"><a href="/">English</a></li> |
|
|
|
<li class="sidebar-nav-item"><a href="/pt">Português</a></li> |
|
|
|
<li class="sidebar-nav-item"><a href="/tags">Tags (EN)</a></li> |
|
|
|
<li class="sidebar-nav-item"><a href="/pt/tags">Tags (PT)</a></li> |
|
|
|
|
|
</ul> |
|
</div> |
|
</div> |
|
|
|
|
|
<div class="content container"> |
|
|
|
<div class="post"> |
|
<h1 class="post-title">Mocking A Mock</h1> |
|
<span class="post-date"> |
|
2016-07-21 |
|
|
|
<a href="https://blog.juliobiason.me/tags/python/">#python</a> |
|
|
|
<a href="https://blog.juliobiason.me/tags/mock/">#mock</a> |
|
|
|
<a href="https://blog.juliobiason.me/tags/mongodb/">#mongodb</a> |
|
|
|
<a href="https://blog.juliobiason.me/tags/find/">#find</a> |
|
|
|
<a href="https://blog.juliobiason.me/tags/count/">#count</a> |
|
|
|
</span> |
|
<p>Mocks are an important part of testing, but learn how to properly mock stuff.</p> |
|
<span id="continue-reading"></span> |
|
<p>A few weeks ago we had a test failing. Now, tests failing is not something |
|
worth a blog post, but the solution -- and the reason it was failing -- is.</p> |
|
<p>A few background information first: The test is part of our Django project; |
|
this project stores part of the information on MongoDB, because the data is |
|
schemaless -- it comes from different sources and each source has its own |
|
format. Because MongoDB is external to our project, it had to be mocked |
|
(sidenote: mocks are there exactly to do this: the avoid having to manage |
|
something external to your project).</p> |
|
<p>PyMongo, the MongoDB driver for Python, has a <code>find()</code> function, pretty much |
|
like the MongoDB API; this function returns a list (or iterator, I guess) with |
|
all the result records in the collection. Because it is a list (iterator, |
|
whatever), it has a <code>count()</code> function that returns the number of records. So |
|
you have something like this:</p> |
|
<pre data-lang="javascript" style="background-color:#2b303b;color:#c0c5ce;" class="language-javascript "><code class="language-javascript" data-lang="javascript"><span style="color:#bf616a;">connector</span><span>.</span><span style="color:#bf616a;">collection</span><span>.</span><span style="color:#96b5b4;">find</span><span>({'</span><span style="color:#a3be8c;">field</span><span>': '</span><span style="color:#a3be8c;">value</span><span>'}).</span><span style="color:#8fa1b3;">count</span><span>() |
|
</span></code></pre> |
|
<p>(Find everything which has a field named "field" that has a value of "value" |
|
and count the results. Pretty simple, right?)</p> |
|
<p>The second hand of information you need is about the <code>mock</code> module. Python 3 |
|
has a module for mocking external resources, which is also available to Python 2. |
|
The interface is the same, so you can |
|
<a href="https://docs.python.org/dev/library/unittest.mock.html">refer to the Python 3 documentation</a> |
|
for both versions.</p> |
|
<p>An usage example would be something like this: If I had a function like:</p> |
|
<pre data-lang="python" style="background-color:#2b303b;color:#c0c5ce;" class="language-python "><code class="language-python" data-lang="python"><span style="color:#b48ead;">def </span><span style="color:#8fa1b3;">request</span><span>(): |
|
</span><span> </span><span style="color:#b48ead;">return </span><span>connector.collection.</span><span style="color:#bf616a;">find</span><span>({'</span><span style="color:#a3be8c;">field</span><span>': '</span><span style="color:#a3be8c;">value</span><span>'}) |
|
</span></code></pre> |
|
<p>and I want to test it, I could this:</p> |
|
<pre data-lang="python" style="background-color:#2b303b;color:#c0c5ce;" class="language-python "><code class="language-python" data-lang="python"><span style="color:#b48ead;">class </span><span style="color:#ebcb8b;">TestRequest</span><span style="color:#eff1f5;">(</span><span style="color:#a3be8c;">unittest.TestCase</span><span style="color:#eff1f5;">): |
|
</span><span> @</span><span style="color:#bf616a;">patch</span><span>("</span><span style="color:#a3be8c;">MyModule.connector.collection.find</span><span>") |
|
</span><span> </span><span style="color:#b48ead;">def </span><span style="color:#8fa1b3;">test_request</span><span>(</span><span style="color:#bf616a;">self</span><span>, </span><span style="color:#bf616a;">mocked_find</span><span>): |
|
</span><span> mocked_find.return_value = [{'</span><span style="color:#a3be8c;">field</span><span>': '</span><span style="color:#a3be8c;">value</span><span>', '</span><span style="color:#a3be8c;">record</span><span>': </span><span style="color:#d08770;">1</span><span>}, |
|
</span><span> {'</span><span style="color:#a3be8c;">field</span><span>': '</span><span style="color:#a3be8c;">value</span><span>', '</span><span style="color:#a3be8c;">record</span><span>': </span><span style="color:#d08770;">2</span><span>}] |
|
</span><span> result = </span><span style="color:#bf616a;">request</span><span>() |
|
</span><span> </span><span style="color:#bf616a;">self</span><span>.</span><span style="color:#bf616a;">assertDictEqual</span><span>(result, mocked_find.return_value) |
|
</span></code></pre> |
|
<p>Kinda sketchy for a test, but I just want to use to explain what is going on: |
|
the <code>@patch</code> decorator is creating a stub for any call for |
|
<code>MyModule.connector.collection.find</code>; inside the test itself, the stub is |
|
being converted to a mock by setting a <code>return_value</code>; when the test is run, |
|
the mock library will intercept a call to the <code>collection.find</code> inside |
|
<code>MyModule.connector</code> (because that module imported PyMongo driver to its |
|
namespace as <code>connector</code>) and return the <code>return_value</code> instead.</p> |
|
<p>Simple when someone explains like this, right? Well, at least I hope you got |
|
the basics of this mocked stuff.</p> |
|
<p>Now, what if you had to count the number of results? It's pretty damn easy to |
|
realize how to do so: just call <code>count()</code> on the resulting list, or make it |
|
return an object that has a <code>count()</code> property. </p> |
|
<p>The whole problem we had was that the result of <code>find()</code> was irrelevant and |
|
all we wanted was the count. Something like</p> |
|
<pre data-lang="python" style="background-color:#2b303b;color:#c0c5ce;" class="language-python "><code class="language-python" data-lang="python"><span style="color:#b48ead;">def </span><span style="color:#8fa1b3;">has_values</span><span>(): |
|
</span><span> elements = connector.collection.</span><span style="color:#bf616a;">find</span><span>({'</span><span style="color:#a3be8c;">field</span><span>': '</span><span style="color:#a3be8c;">value</span><span>'}).</span><span style="color:#bf616a;">count</span><span>() |
|
</span><span> </span><span style="color:#b48ead;">return </span><span>elements > </span><span style="color:#d08770;">1 |
|
</span></code></pre> |
|
<p>First of all, you can't patch <code>MyModule.connector.collection.find.count</code> |
|
because you'll only stub the <code>count</code> call, not <code>find</code>, which will actually try |
|
to connect on MongoDB; so the original patch is required. And you can't patch |
|
both <code>find</code> and <code>count</code> because the first patch will return a new <code>MagicMock</code> |
|
object, which will not be patched (after all, it is <em>another</em> object). The |
|
original developer tried to fix it this way:</p> |
|
<pre data-lang="python" style="background-color:#2b303b;color:#c0c5ce;" class="language-python "><code class="language-python" data-lang="python"><span>mocked_find.count.return_value = </span><span style="color:#d08770;">0 |
|
</span></code></pre> |
|
<p>... which, again, doesn't work because the call to <code>find()</code> will return a |
|
<code>MagicMock</code> that doesn't have its <code>count</code> patched. But the developer never |
|
realized that because <code>MagicMock</code> tries its best to <em>not</em> blow up your tests, |
|
including having return values to conversions like... int. And it will always |
|
return 1.</p> |
|
<p>Is your head spinning yet? Mine sure did when I realized the whole mess it was |
|
being made. And let me repeat this: The problem was <em>not</em> that MongoDB was |
|
being mocked, but that it was being <em>mocked in the wrong way</em>.</p> |
|
<p>The solution? As pointed above, make <code>find</code> return an object with a <code>count</code> |
|
method.</p> |
|
<pre data-lang="python" style="background-color:#2b303b;color:#c0c5ce;" class="language-python "><code class="language-python" data-lang="python"><span>count_mock = </span><span style="color:#bf616a;">MagicMock</span><span>(</span><span style="color:#bf616a;">return_value</span><span>=</span><span style="color:#d08770;">0</span><span>) |
|
</span><span>mocked_find.return_value = </span><span style="color:#bf616a;">MagicMock</span><span>( |
|
</span><span> **{'</span><span style="color:#a3be8c;">count</span><span>': count_mock}) |
|
</span></code></pre> |
|
|
|
</div> |
|
|
|
|
|
|
|
|
|
</div> |
|
|
|
</body> |
|
|
|
</html>
|
|
|