(lists.tests.test_views.NewListViewIntegratedTest)
File "/workspace/superlists/lists/views.py", line 30, in new_list2
return redirect(list_)
[...]
TypeError: argument of type 'NoneType' is not iterable
FAILED (errors=3)
Here’s an important lesson to learn about test isolation: it might help you to drive out
good design for individual layers, but it won’t automatically verify the integration
be‐
tween
your layers.
What’s happened here is that the view was expecting the form to return a list item:
lists/views.py.
list_
=
form
.
save
(
owner
=
request
.
user
)
return
redirect
(
list_
)
But we forgot to make it return anything:
lists/forms.py.
def
save
(
self
,
owner
):
if
owner
.
is_authenticated
():
List
.
create_new
(
first_item_text
=
self
.
cleaned_data
[
'text'
],
owner
=
owner
)
else
:
List
.
create_new
(
first_item_text
=
self
.
cleaned_data
[
'text'
])
Thinking of Interactions Between Layers as “Contracts”
Ultimately, even if we had been writing nothing but isolated unit tests, our functional
tests would have picked up this particular slip-up. But ideally we’d want our feedback
cycle to be quicker—functional tests may take a couple of minutes to run, or even a few
hours once your app starts to grow. Is there any way to avoid this sort of problem before
it happens?
Methodologically, the way to do it is to think about the interaction between your layers
in terms of contracts. Whenever we mock out the behaviour of one layer, we have to
make a mental note that there is now an implicit contract between the layers, and that
a mock on one layer should probably translate into a test at the layer below.
Here’s the part of the contract that we missed:
lists/tests/test_views.py.
@patch
(
'lists.views.redirect'
)
def
test_redirects_to_form_returned_object_if_form_valid
(
self
,
mock_redirect
,
mockNewListForm
):
mock_form
=
mockNewListForm
.
return_value
mock_form
.
is_valid
.
return_value
=
True
response
=
new_list2
(
self
.
request
)
Thinking of Interactions Between Layers as “Contracts”
|
355