#33348 closed Cleanup/optimization (fixed)
Change API of assertFormError to take an actual form object
Reported by: | Baptiste Mispelon | Owned by: | Baptiste Mispelon |
---|---|---|---|
Component: | Testing framework | Version: | dev |
Severity: | Normal | Keywords: | |
Cc: | Baptiste Mispelon | Triage Stage: | Ready for checkin |
Has patch: | yes | Needs documentation: | no |
Needs tests: | no | Patch needs improvement: | no |
Easy pickings: | no | UI/UX: | no |
Description
The current signature of assertFormError
is (response, form, field, errors, msg_prefix='')
where:
response
is a response object (specifically one generated by the test client because it needsresponse.context
)form
is the name of a form (string) to be found in the response's contextfield
is a name of a field in the form (string)errors
is either a list of error strings, a single error string (equivalent to[error]
) orNone
(equivalent to[]
)msg_prefix
is a string added to the test failure message
Personally I've always found this response
/form
API to be clunky since I often want to test a form directly without having to go through the test client.
It would be a lot easier to use if we could pass a form object directly. I don't really understand why the API was implemented like this to begin with.
On top of that, now that Django uses template-based rendering for forms, formsets and widgets there are a lot of contexts to search in response.context
and a lot of possibilities for clashing names (see #33346).
I would therefore propose the following new signature: assertFormError(form, field, errors, msg_prefix='')
with form
being an actual Form
object and all other parameters staying the same.
The deprecation should be straightforward to implement by checking if form
is a response object (isinstance
check) or if response
kwarg has been passed.
With that change assertFormsetError
becomes mostly useless since one can simply do assertFormError(formset.forms[i], field_name, errors)
. The one usecase that wouldn't be covered is i = None + field_name=None
which checks the errors
list against the formset's non_form_errors()
.
We could either leave assertFormsetError
in place as a convenience method (with a similar change to its API to accept a formset object directly) or deprecate more agressively by suggesting the user tests the output of formset.non_field_errors()
directly.
Change History (15)
comment:1 by , 3 years ago
comment:2 by , 3 years ago
Triage Stage: | Unreviewed → Accepted |
---|
Agreed, let's change the first argument to form
/formset
and deprecate passing a response
in it.
comment:3 by , 3 years ago
Has patch: | set |
---|
PR is ready for review: https://github.com/django/django/pull/15179
Overall the changes are backwards-compatible but there are some exceptions:
1) The failure messages are now different
2) The behavior of errors=[]
is completely different
3) The behavior in the case of multiple errors
is different
For 1), I don't know if our compatibility policy applies here. As noted in comment:1 the failure message were already prone to change arbitrarily as Django rendered more and more templates in its request/response cycle.
For 2), the old implementation of assertFormError
(and assertFormsetError
) would always pass when using errors=[]
. Even if the field had no errors, or even if the field was not present on the form. I'd argue that this is a prety nasty bug and fixing it actually caught an incorrect tests in Django's own test suite [1]
For 3), the old implementation would only make sure that all the errors
passed to assertFormError
(and assertFormsetError
) were present in the field's error. If the field happened to have extra errors the test would still pass. I changed that behavior so that the two lists must match exactly. This makes the implementation simpler (we can use a plain assertEqual
and let Python give a nice diff in case of errors) and I think it's also more correct. The documentation wasn't very clear anyway so I think the change is acceptable.
As part of the ticket, I also removed the undocumented ability to use errors=None
as an equivalent to errors=[]
. I don't really see the usecase for that "shortcut" and "one obvious way" something something.
Finally, the PR adds a nicer repr
for formsets, similar to what was done in #23167 (it made the tests easier to write).
[1] https://github.com/django/django/blob/2f73e5406d54cb8945e187eff302a3a3373350be/tests/admin_views/tests.py#L5558
comment:4 by , 3 years ago
Owner: | changed from | to
---|
comment:5 by , 3 years ago
Patch needs improvement: | set |
---|
comment:12 by , 3 years ago
Patch needs improvement: | unset |
---|---|
Triage Stage: | Accepted → Ready for checkin |
As an argument in favor of this change, may I present this lines from
tests/admin_views/tests.py:6540
[1]Over the years as more and more templates got involved in the rendering of admin views, the exact number needed to make this test pass went from 4 to 10 to 12 to 22 (and every time, I suspect someone changed it manually to whichever new number the test failure asked for).
With the change I propose, the test would look like this:
Though on second thought, in this case I think we can skip
assertFormsetError
altogether:[1] https://github.com/django/django/blob/8a4e5067605e608c3fcbb5ca11e0019eac8b40aa/tests/admin_views/tests.py#L6540