I'm building a web framework on top of Hunchentoot and I ran into problems while trying to unit test some of my code. Essentially I need to unit test behavior that happens accross requests. I want to do it without starting Hunchentoot by faking the necessary objects (request, session, etc.)
The documentation says the usual special variables passed to handlers contain opaque objects. Hunchentoot provides no means of instantiating them manually (unless I get into internal code, which will tie me to the implementation). I see two possible solutions:
1. Abstract my code away from Hunchentoot objects. This way I can fake the necessary environment during unit testing and use Hunchentoot's environment during the real thing.
2. Create my own objects and make sure all appropriate symbols are defined for them - essentially faking Huchentoot's API.
It seems like the first option is a better one, but I feel like Huchentoot should provide some support for this. I'd be happy to hack it in if I get some direction (as I'm new to Hunchentoot and CL in general).
Slava Akhmechet wrote:
I'm building a web framework on top of Hunchentoot and I ran into problems while trying to unit test some of my code. Essentially I need to unit test behavior that happens accross requests. I want to do it without starting Hunchentoot by faking the necessary objects (request, session, etc.)
I had a similar problem, and I guess my not very elegant solution was to create a dummy-request class, which basically has the same slots as that of hunchentoot::request, and you can set parameters like so, and set the dummy request instance to *request*:
(defclass dummy-request () ((headers-in :initarg :headers-in :initform nil) (method :initarg :method) (uri :initarg :uri) (server-protocol :initarg :server-protocol) (content-stream :initarg :content-stream :reader content-stream) (cookies-in :initform nil) (get-parameters :initform nil) (post-parameters :initform nil) (script-name :initform nil) (query-string :initform nil) (session :initform nil :accessor hunchentoot::session) (aux-data :initform nil :accessor hunchentoot::aux-data) (raw-post-data :initform nil)))
And then:
(let ((*request* (make-instance 'dummy-request))) (setf (slot-value *request* 'method) :post) (setf (slot-value *request* 'post-parameters) '(("id" . "10") ("name" . "Chicago")))
And you can access this in the regular way:
(setf some-var (get-parameter "id"))
I found out that setting some vars like hunchentoot::*session-secret* properly and doing a start-session lets you use session within a test happily. For most tests that use session/request related code, I do something like this and put it in a macro:
(let* ((hunchentoot::*remote-host* "localhost") (hunchentoot::*session-secret* (hunchentoot::reset-session-secret)) (hunchentoot::*reply* (make-instance 'hunchentoot::reply)) (*request* (make-instance 'dummy-request)) (*session* (start-session)))
I'm not sure if this is the best way to do this, but this approach works fine for me.
HTH, Vamsee.
Vamsee Kanakala vamlists@gmail.com writes:
I had a similar problem, and I guess my not very elegant solution was to create a dummy-request class, which basically has the same slots as that of hunchentoot::request, and you can set parameters like so, and set the dummy request instance to *request*:
Thanks! This is essentially what I wanted to do but you handed it to me on a silver platter :) One question though, why do you create a dummy-request class instead of using hunchentoot's? You could also derive something like unittest-request if you need some special functionality.
I found out that setting some vars like hunchentoot::*session-secret* properly and doing a start-session lets you use session within a test happily.
Would it make sense to add some functions to Hunchentoot that would provide a public API to do this? This way everyone can unit-test Hunchentoot apps in the same standard way.
Slava Akhmechet wrote:
Thanks! This is essentially what I wanted to do but you handed it to me on a silver platter :) One question though, why do you create a dummy-request class instead of using hunchentoot's? You could also derive something like unittest-request if you need some special functionality.
If I remember correctly, instantiating hunchentoot::request directly is a bit of a problem as it depends on things like headers-in to be set properly, which needs the server to be started, or at least instantiate a hunchentoot::server object. It didn't seem very efficient as I would be running hundreds of tests, and many of them use request params. So I wanted something very simple to just mock hunchentoot::request's behavior in setting and retrieving params. And I was also too lazy to see if a more generic approach could be taken to this :). So yes, unittest-request looks like a good idea.
Would it make sense to add some functions to Hunchentoot that would provide a public API to do this? This way everyone can unit-test Hunchentoot apps in the same standard way.
Though I can't say I had a detailed look all the unit test frameworks available out there, but I wasn't satisfied with the ones available, so basically I wrote my own on top of Peter Seibel's code in his PCL book. So a unit test framework that is more web-oriented, hunchentoot-compatible seems like a great idea.
Actually there are a couple of times I thought of writing about the same issue to this list, but one thing I was very short on time, and some meddling around seemed to solve my problem. Since you're writing a web framework based on Hunchentoot, seems like we're solving the same problems (I'm adding these bits as I write my webapp) - would love to collaborate where I can.
Regards, Vamsee.
Vamsee Kanakala vamlists@gmail.com writes:
If I remember correctly, instantiating hunchentoot::request directly is a bit of a problem as it depends on things like headers-in to be set properly, which needs the server to be started, or at least instantiate a hunchentoot::server object.
Ahh, I see.
Though I can't say I had a detailed look all the unit test frameworks available out there, but I wasn't satisfied with the ones available, so basically I wrote my own on top of Peter Seibel's code in his PCL book. So a unit test framework that is more web-oriented, hunchentoot-compatible seems like a great idea.
I wasn't thinking about writing a unit test framework (there are plenty of those, I don't know how adding another one would be beneficial). I was just thinking about making it easy to unit test hunchentoot apps. It looks like you've already given all the information on how to do that, it's just a matter of making it part of hunchentoot (assuming it makes sense).
Since you're writing a web framework based on Hunchentoot, seems like we're solving the same problems (I'm adding these bits as I write my webapp) - would love to collaborate where I can.
I'll make it Open Source when I'm ready (probably in a few months). My code is currently in a hectic state so I don't want to publish it at the moment. I'd love to discuss any and all web-related issues, though.
On Sun, 15 Apr 2007 20:32:04 -0400, Slava Akhmechet coffeemug@gmail.com wrote:
It looks like you've already given all the information on how to do that, it's just a matter of making it part of hunchentoot (assuming it makes sense).
I'm not yet convinced that that would make sense. I can certainly see the benefit of being able to easily test web applications written with Hunchentoot, but I wonder how much of its internals Hunchentoot should expose for this. If you have to add a lot of code for this, it will be hard to maintain and keep in sync with Hunchentoot, and the chance of the test facilities behaving differently from Hunchentoot itself will increase.
Also, I'm not sure if the test suite for a web application shouldn't be in an image different from the web server and operate as a web client. That way it'll be much closer to a real-world scenarion you want to test. Can you point me to other web servers that provide "hooks" for unit testing them without external clients?
Edi Weitz edi@agharta.de writes:
I'm not yet convinced that that would make sense.
BTW, another bit I had to hack was the *server* parameter and class to get 'request-method' to work. Something like this:
(defclass unittest-server () ((mod-lisp-p :initform nil :initarg :mod-lisp-p)))
(defmethod hunchentoot::server-mod-lisp-p ((obj unittest-server)) (slot-value obj 'mod-lisp-p))
(let ((*server* (make-instance 'unittest-server))) ...)
May be when I work through some more of these issues I can release a separate project, sort of a Hunchentoot addon, for unit-testing in this manner.
I still think that a client library is too heavy - it would provide a different kind of test (also valueable, but different).
On Mon, 16 Apr 2007 10:48:26 -0400, Slava Akhmechet coffeemug@gmail.com wrote:
May be when I work through some more of these issues I can release a separate project, sort of a Hunchentoot addon, for unit-testing in this manner.
Yes, I think releasing this as some kind of addon would make much more sense than it becoming part of the core of Hunchentoot.