Feature proposal: Test client form value extraction

8 views
Skip to first unread message

Joshua Russo

unread,
Aug 25, 2009, 3:09:46 PM8/25/09
to django-d...@googlegroups.com
I've just put together an enhancement of the test Client used in unit testing. I added the parsing of the content to extract the form fields with their initial values as a dictionary, so you can just change a few values and throw it back at the server.


I had to do a small monkey patch to get it to work because direct override of the __ call __ method interfered with the transaction management for some reason.

Let me know what you think. Should I submit it as a ticket?

Joshua Russo

unread,
Aug 25, 2009, 6:18:54 PM8/25/09
to Django developers
On Aug 25, 6:09 pm, Joshua Russo <josh.r.ru...@gmail.com> wrote:
> I've just put together an enhancement of the test Client used in unit
> testing. I added the parsing of the content to extract the form fields with
> their initial values as a dictionary, so you can just change a few values
> and throw it back at the server.http://dpaste.com/hold/85281/
>
> I had to do a small monkey patch to get it to work because direct override
> of the __ call __ method interfered with the transaction management for some
> reason.
>
> Let me know what you think. Should I submit it as a ticket?

Here is a basic usage example:

def test_htmlParse(self):

response = self.client.get('/test/')
self.failUnlessEqual(response.status_code, 200, 'Failed to
retrieve the form test page. (1)')

curVals = response.form_values['frmTest']

response = self.client.post('/test/', curVals)
self.failUnlessEqual(response.status_code, 200, 'Failed to
retrieve the form test page. (2)')

Thomas Guettler

unread,
Aug 26, 2009, 4:11:46 AM8/26/09
to django-d...@googlegroups.com
Hi,

some time ago I wrote a snippet which does this:

http://www.djangosnippets.org/snippets/467/

{{{
If you want to test a post request to a form, you need to give all input fields, even if you just want to test if one
value gets changed.

This snippets parses the HTML of a view and gives you a dictionary that you can give to django.test.Client.

Needs ClientForm from: http://wwwsearch.sourceforge.net/ClientForm/
}}}


Joshua Russo schrieb:

--
Thomas Guettler, http://www.thomas-guettler.de/
E-Mail: guettli (*) thomas-guettler + de

Joshua Russo

unread,
Aug 26, 2009, 4:41:56 AM8/26/09
to django-d...@googlegroups.com
I figured someone had done this at some time. 

What's the general consensus on when some thing like this should be considered to be added to the project and when it should just stay as an outside utility?

Russell Keith-Magee

unread,
Aug 26, 2009, 9:52:48 AM8/26/09
to django-d...@googlegroups.com
On Wed, Aug 26, 2009 at 4:41 PM, Joshua Russo<josh.r...@gmail.com> wrote:
> I figured someone had done this at some time.
> What's the general consensus on when some thing like this should
> be considered to be added to the project and when it should just stay as an
> outside utility?

It's certainly worth putting up as a ticket. I can agree with the
original use case - that is, using the client.get() to construct the
prototype data dictionary that will be passed to client.post(). You
only have to look at the work that is done in the admin tests to see
the value in this proposal.

However, I have some reservations about the patch.

Most notably, I'm not sure I see how your patch answers the original
use case. The form data structure that is returned by the parser
appears to contain a top-level dictionary containing the form
instances. The form structure itself is irrelevant to reposting.

However, thats not to say that keeping the form data is irrelevant.
For example, consider the case where a page has multiple forms, or
multiple submit buttons on a single form. If we're going to introduce
a utility like this, then it should be able to behave at least
partially like a browser - that is, I should be able to get the post
data that is needed to respond to a specific button press:

response = self.client.get('/path/to/page')
# Get a copy of a data dictionary that contains the form data
# that would be submitted if I pressed the submit button labeled 'button 3'
data = response.form_data('button 3')
data['some-field'] = 'new value'
response = self.client.post('/path/to/page', data)

We also need to be careful of feature creep - this sort of testing is
exactly what Twill does, and we don't want to duplicate the efforts of
that project (or any other automated Python client test framework for
that matter).

Yours,
Russ Magee %-)

Joshua Russo

unread,
Aug 26, 2009, 10:34:45 AM8/26/09
to django-d...@googlegroups.com
That seems reasonable. I'll work on it and let you know when I have an official patch ready.

Joshua Russo

unread,
Aug 27, 2009, 8:54:44 AM8/27/09
to django-d...@googlegroups.com
Let me know how this looks.


The only question I have is when I encounter a Form that doesn't have a name or an id, should the index be a real number or should I convert it into a string for consistency?

Joshua Russo

unread,
Aug 27, 2009, 12:28:03 PM8/27/09
to django-d...@googlegroups.com
Ok, so I found that the way I was 'casting' the response object didn't work. Is there no way to cast an instance of a base class to a child class in Python? 

What I did was to create a class method on my child class that takes the current response instance and creates a copy of it using my new response object.


Does this look appropriate, or is there a better way to do this?

Forest Bond

unread,
Aug 27, 2009, 3:22:52 PM8/27/09
to django-d...@googlegroups.com
Hi,

This should work:

response.__class__ = TestHttpResponse

-Forest
--
Forest Bond
http://www.alittletooquiet.net
http://www.pytagsfs.org

signature.asc

Joshua Russo

unread,
Aug 27, 2009, 4:42:24 PM8/27/09
to django-d...@googlegroups.com
On Thu, Aug 27, 2009 at 6:22 PM, Forest Bond <for...@alittletooquiet.net> wrote:
Hi,

On Thu, Aug 27, 2009 at 03:28:03PM -0100, Joshua Russo wrote:
> On Thu, Aug 27, 2009 at 11:54 AM, Joshua Russo <josh.r...@gmail.com> wrote:
> Ok, so I found that the way I was 'casting' the response object didn't work. Is
> there no way to cast an instance of a base class to a child class in Python? 
>
> What I did was to create a class method on my child class that takes the
> current response instance and creates a copy of it using my new response
> object.
>
>  http://dpaste.com/hold/86193/
>
> Does this look appropriate, or is there a better way to do this?

This should work:

 response.__class__ = TestHttpResponse

Nope, it resets the status_code. That's what I tried at first.

Forest Bond

unread,
Aug 27, 2009, 7:39:06 PM8/27/09
to django-d...@googlegroups.com
Hi,

Okay, you'd have to copy the class attributes, too. Or, you could just define a
sub-class on-the-fly:

class _TestHttpResponse(TestHttpResponse, response.__class__):
pass

response.__class__ = _TestHttpResponse

I haven't tested this.

signature.asc

Joshua Russo

unread,
Aug 28, 2009, 1:11:25 PM8/28/09
to django-d...@googlegroups.com
That might just work. I would need to to change the base class of TestHttpResponse to Object, but then my class wouldn't (shouldn't) override the status_code in the response class. Good idea. Though I may have to wait until Monday to test it out. 

Thanks

Joshua Russo

unread,
Aug 28, 2009, 1:37:34 PM8/28/09
to django-d...@googlegroups.com
Yup that works! Thanks!

Joshua Russo

unread,
Aug 31, 2009, 4:01:07 PM8/31/09
to django-d...@googlegroups.com
Building off your suggestion Forest I've actually built a function that is similar to a factory function.

def castHttpResponse(curClass):
    class TestHttpResponse(curClass):

        def form_data(self, search_name):
            """
            Returns the entire set of form values associated with form of the given field or form name
            """
            if getattr(self, "_form_data", None) is None:
                self._form_data = self._get_form_data()
            if self._form_data.has_key(search_name):
                return self._form_data[search_name]
            for form_name, data in self._form_data.items():
                if data.has_key(search_name):
                    return data
            raise FieldNotFound("A field or form with the name %s was not found." % search_name)

        def _get_form_data(self):
            curParser = FormHTMLParser()
            curParser.feed(self.content)
            curParser.close()
            return curParser.forms
    
    return TestHttpResponse

And then I can just call the function like so:
response.__class__ = castHttpResponse(response.__class__)

I think this is cleaner as it avoids the multiple inheritance.
Reply all
Reply to author
Forward
0 new messages