Tutorial 13 - Better Safe Than Sorry (Part 1)

February 17, 2018 · View on GitHub

In this tutorial we're going to prepare the field for one of the main topics in the software development life cycle: code testing. Code testing is a kind of continuum which goes from zero to almost full test coverage. I'm not going to open one of those endless discussions about the right amount of testing. Code testing is necessary. How much testing is needed? It depends.

I have to admit that I have never created a program by first writing a failing unit test, then writing the code to make the test succeed. When you are as old as I am, you can't change your habits. So, if you are religious about TDD (Test-Driven Development) / BDD (Behavior-Driven Development), please forgive me.

Nowadays, using functional programming languages like CLJ/CLJS, the unit tests are much easier to implement, compared with imperative and object-oriented programming languages, since most of the time you are dealing with pure functions whose output depends only on their input.

Preamble

To start working from the end of the previous tutorial, assuming you've git installed, do as follows.

git clone https://github.com/magomimmo/modern-cljs.git
cd modern-cljs
git checkout se-tutorial-12

Introduction

Before we go ahead with the problem of testing your CLJ/CLJS code, we have to finish something we left behind in the previous tutorials. In Tutorial 9 - Introducing Ajax - we implemented a Shopping Calculator by using the Ajax style of communication between the browser (i.e. ClojureScript) and the server (i.e. Clojure).

Obviously, nobody will ever implement that kind of stupid widget by using Ajax, because all the information needed to make the calculation from the input is already present on the browser side. By moving the calculation of the Total from the client side to the server side, we found an excuse to gently introduce a little bit of Ajax in CLJS/CLJ.

Nonetheless, by failing to implement the server-side only Shopping Calculator, we have broken the first principle of the progressive enhancement strategy, which dictates:

Start to develop the front-end by ignoring the existence of JavaScript for a while.

But we're in a good position to cover the missing step, because we have the same language on both sides. It should not be a PITA to go from one side to the other and vice-versa. Just be prepared to pay a little attention anytime you cross the border in either direction.

And remember, you can even move the border, if this is useful for any reason. By moving the border I mean that you can enable pieces of your code to be movable at will from one side to the other side of the CLJ/CLJS border.

Add Enlive

In this tutorial we're going to use Enlive by Christophe Grand, one of the most famous libraries in the Clojure ecosystem. Even though it will take few paragraphs before we see it in action, we begin by adding it to build.boot before starting the IFDE.

Open the build.boot file and add enlive to the dependencies section as usual with any lib:

(set-env!
 ...
 :dependencies '[..
                 [enlive "1.1.6"]
                 ])
...

Review the Shopping Calculator

ATTENTION NOTE: all the figures of this tutorials have been taken a couple of years ago with old versions of Chrome/Canary that are now outdated. That said, Mutatis Mutandis, you should be able to follow the tutorial without any problem.

Start the IFDE as usual

cd /path/to/modern-cljs
boot dev
...
Elapsed time: 19.288 sec

and visit the shopping URI for reviewing the Shopping Calculator program.

Click the Calculate button. The Total field is populated with the result of the calculation executed via Ajax on the server-side.

Note that the localhost:3000/shopping.html URI shown in the address bar of the browser does not change when you click the Calculate button. Only the value of the Total field will be updated, even if the calculation was executed by the server. That's one of the beauties of Ajax.

As you already know from Tutorial 9 - Introducing Ajax, you can open the Developer Tools panel of your browser (I'm using Google Chrome) and take a look at the Network tab after having reloaded the shopping URI.

Any time you click the Calculate button a new _shoreleave POST asynchronous method request is submitted to the server which responds with the HTTP/1.1 202 status code (i.e. Accepted).

AjaxNetwork

But what happens if you disable JavaScript? Let's try.

Disable JavaScript and then reload the shopping URI. If you move the mouse cursor over the Calculate button nothing happens and nothing happens even if you click it.

Take a look at the shopping.html file which is under the html directory of the modern-cljs main directory.

<!doctype html>
<html lang="en">
<head>
        ...
        ...
</head>
<body>
  <!-- shopping.html -->
  <form id="shoppingForm" novalidate>
    <legend> Shopping Calculator</legend>
    <fieldset>
    ...
    ...
      <div>
        <input type="button"
               value="Calculate"
               id="calc">
      </div>
    </fieldset>
  </form>
    ...
        ...
</body>
</html>

As you can see, the form tag has no action or method attributes and the type attribute of the Calculate input is set to button. This means that, when JavaScript is disabled, the form does not respond to any event.

Step 1 - Break the Shopping Calculator

Now modify the shoppingForm by adding the action="/shopping" and the method="POST" attribute/value pairs. Next, change the Calculate input from type="button" to type="submit" attribute/value pair. Following is the interesting snippet of the modified HTML code.

  ...
  <form action="/shopping" id="shoppingForm" method="POST" novalidate>
    <legend> Shopping Calculator</legend>
    <fieldset>
    ...
    ...
      <div>
        <input type="submit"
               value="Calculate"
               id="calc">
      </div>
    </fieldset>
  </form>
  ...
  ...
</html>

Reload the shopping URI and click the Calculate button again. You receive a plain Page not found text from the server. You also see the localhost:3000/shopping URI in the address bar of your browser instead of the previous localhost:3000/shopping.html URI.

In the Tutorial 9 - Introducing Ajax we used Compojure to define the application's routes as follows:

(defroutes handler
  (GET "/" [] "Hello from Compojure!")  ;; for testing only
  (files "/" {:root "target"})          ;; to serve static resources
  (POST "/login" [email password] (authenticate-user email password))
  (resources "/" {:root "target"})      ;; to serve anything else
  (not-found "Page Not Found"))

It explains why we received the Page not found page: we did not define any route for the localhost:3000/shopping URI requested by the Calculate button.

By setting the method attribute of the shoppingForm to POST and the type attribute of its calc input field to submit, we are asking the browser to send a POST request with the localhost:3000/shopping URI whenever the user clicks the Calculate button and this URI does not exist.

A kind of TDD

By modifying the shopping.html file and disabling the JavaScript from the browser, we have just exercised a kind of TDD (Test-Driven Development) approach.

To fix the failure we just met, we need to add a route for the "/shopping" request to the defroutes macro call.

Open the src/clj/modern_cljs/core.clj file and add the "/shopping" POST route:

(defroutes handler
  ...
  (POST "/shopping" [quantity price tax discount]
        (str "You entered: "quantity " " price " " tax " and " discount "."))
  ...)

NOTE 1: In the RESTful community, which I respect a lot, this would be a blasphemy, since the Shopping Calculator is an application resource which, in RESTful parlance, is safe and idempotent and we should have used the default GET verb/method.

NOTE 2: We extract the values of the input parameters of the shoppingForm by passing the args vector [quantity price tax discount] to the POST call.

Now click again the Calculate button. You should receive as plain text the input values of the form.

FictionShopping

Let's now go back for a while and see what happens if we re-enable the JavaScript engine of the browser.

Enable JS and reload the shopping URI and finally click the Calculate button again.

FictionShopping2

Ops, it seems that the Ajax version of the Shopping Calculator does not work anymore. What happened?

Fix the failed test

Now that we have changed the Calculate input from type="button" to type="submit", when the user clicks it, the control passes to the action="/shopping" and submits a POST request to the server. The server then responds by calling the handler function which is now associated with the POST "/shopping" route.

We have dealt with this problem in a previous tutorial dedicated to the login example. We solved it by preventing the above from happening. We need to use the same code in src/cljs/modern_cljs/shopping.cljs.

Open the shopping.cljs file and modify the function associated with the click event as follows.

(defn ^:export init []
  (when (and js/document
             (aget js/document "getElementById"))
    (listen! (by-id "calc")
             :click
             (fn [evt] (calculate evt)))
    (listen! (by-id "calc")
             :mouseover
             (fn []
               (append! (by-id "shoppingForm")
                        (html [:div.help "Click to calculate"]))))
    (listen! (by-id "calc")
             :mouseout
             (fn []
               (destroy! (by-class "help"))))))

We wrapped the calculate function inside an anonymous function, which now receives an event as the sole argument.

Now we need to modify the calculate function definition to prevent the click event from being passed to the action of the Shopping form.

(defn calculate [evt]
  (let [quantity (read-string (value (by-id "quantity")))
        price (read-string (value (by-id "price")))
        tax (read-string (value (by-id "tax")))
        discount (read-string (value (by-id "discount")))]
    (remote-callback :calculate
                     [quantity price tax discount]
                     #(set-value! (by-id "total") (.toFixed % 2)))
    (prevent-default evt)))

We updated the signature of the calculate function to accept the event and added the (prevent-default evt) as the last call in its definition, which interrupts the flow.

The last modification we have to introduce is to add the prevent-default symbol to the :refer section of the domina.events requirement as follows:

(ns modern-cljs.shopping
  (:require [domina.core :refer [append!
                                 by-class
                                 by-id
                                 destroy!
                                 set-value!
                                 value]]
            [domina.events :refer [listen! prevent-default]]
            [hiccups.runtime]
            [shoreleave.remotes.http-rpc :refer [remote-callback]]
            [cljs.reader :refer [read-string]])
  (:require-macros [hiccups.core :refer [html]]
                   [shoreleave.remotes.macros :as macros]))

As soon as you save the file, everything gets recompiled. As you remember, when we modify the exported init function we have to reload the corresponding page which calls it from the script tag.

Reload the shopping URI. You should now see the Ajax version of the Shopping Calculator working as expected.

Not bad so far.

Step 2 - Enliving the server-side

In the previous pragraphs of this tutorial we set the stage for introducing Enlive.

There are already several Enlive tutorials available online and I'm not going to add anything beyond the simplest use case to allow us to implement the server-side only Shopping Calculator in accordance with the progressive enhancement principle.

The reasons I chose Enlive are very well motivated by David Nolen in his nice tutorial on Enlive:

Enlive gives you the advantages of designer accessible templates (since they’re just HTML) without losing the power of function composition. As a result, your designer can create all the various widgets for your website using only HTML and CSS and you can compose your pages from any combination of their designs.

This is similar to Domina separation of concern which allows the designer and the programmer to play their roles without too many impedance mismatches.

Our needs are very easy to describe. We have to:

  1. Read a pure HTML template/page from the file system representing the Shopping Calculator;
  2. Read the parameters typed in by the user from the submitted HTTP request;
  3. Parse the extracted values and calculate the total;
  4. Update the fields in the HTML form; and
  5. Send the resulting page to the user.

The following picture shows a sequence diagram of the above description.

Shopping-server

Obviously we should also validate all inputs, but this is something we'll take care of later in the next tutorial.

Enter Enlive

Steps 2. and 5. are already satisfied by the defroutes macro from Compojure. Step 3. - calculate the total - seems to be already satisfied by the the defremote macro call from Shoreleave, which implicitly defines a function with the same name.

It seems that we just need to implement step 1. (read the shopping.html file from its directory) and step 4. (update the input fields of the form).

Enlive offers a single macro, deftemplate, which allows us to solve both steps 1. and 4. in a single shot.

deftemplate accepts 4 arguments:

  • name
  • source
  • args
  • & forms.

It implicitly creates a function with the same number of args and the same name as the template. The source argument can be any HTML file located in the classpath of the application.

Finally, the &forms argument is composed of a sequence of pairs. The first item from each pair is a vector of CSS-like selectors, used to select the interested elements/nodes from the parsed HTML source. The second item from each pair is a function which is applied to transform each selected element/node.

As you perhaps remember from the tutorial introducing Ajax, in build.boot the serve task had its :resource-root set to the "target" directory.

(deftask dev
  "Launch immediate feedback dev environment"
  []
  (comp
   (serve :handler 'modern-cljs.remotes/app            ;; ring handler
          :resource-root "target"                      ;; root classpath
          :reload true)                                ;; reload ns
   (watch)
   (reload)
   (cljs-repl) ;; before cljs
   (cljs)
   (target :dir #{"target"})))

This means that we can pass the shopping.html file to deftemplate as the source argument.

For the name argument, we're going to use the same POST route (i.e. shopping) previously defined inside the defroutes macro.

Then, the args to be passed to deftemplate are the same ones as in the "/shopping" POST route: [quantity price tax discount].

Finally, regarding the & forms arguments, start by using two nil values, which means no selectors and no transformations. We should now expect that the source will be rendered exactly as the original HTML source.

Let's code

We have to decide where to create the CLJ file containing the template definition for the Shopping Calculator page. I prefer to maintain a directory structure which mimics the logical structure of an application. So I decided to create a new templates directory under the src/clj/modern_cljs/ directory.

# in a new terminal
cd /path/to/modern-cljs
mkdir src/clj/modern_cljs/templates

Inside this directory create the shopping.clj file where we'll create the deftemplate macro call.

touch src/clj/modern_cljs/templates/shopping.clj

Following is the content of the newly created shopping.clj file to define the Shopping Calculator template

(ns modern-cljs.templates.shopping
  (:require [net.cgrand.enlive-html :refer [deftemplate]]))

(deftemplate shopping "shopping.html"
  [quantity price tax discount]
  nil nil)

Now that we have defined the shopping template, which implicitly defines the shopping function, we can go back to the core.clj to update its namespace declaration and substitute the (str "You enter: " quantity " " price " " tax " and " discount ".") call with the call to the newly defined shopping function.

(ns modern-cljs.core
  (:require [compojure.core :refer [defroutes GET POST]]
            [compojure.route :refer [not-found files resources]]
            [modern-cljs.login :refer [authenticate-user]]
            [modern-cljs.templates.shopping :refer [shopping]]))

(defroutes handler
  (GET "/" [] "Hello from Compojure!")  ;; for testing only
  (files "/" {:root "target"})          ;; to serve static resources
  (POST "/login" [email password] (authenticate-user email password))
  (POST "/shopping" [quantity price tax discount]
        (shopping quantity price tax discount))
  (resources "/" {:root "target"})      ;; to serve anything else
  (not-found "Page Not Found"))         ;; page not found

Now disable the JavaScript engine of your browser again and visit the shopping URI.

You should see the Shopping Calculator page showing the default field values again and again each time you press the Calculate button, no matter what you typed in the value boxes of the fields. This is exactly what we expected, because we did not select any node and any transformation of the nodes. So far so good.

Select and transform

It's now time to fill the gap in the deftemplate call by adding the appropriate selector/transformation pairs.

For a deeper understanding of the CSS-like selectors accepted by deftemplate, you need to understand CSS selectors. You need to know this anyway if you want to use Domina, JQuery, etc. So, even if we'd like to have a unified language for everything, you can't avoid learning a little bit of HTML, CSS and JS to use CLJ/CLJS. That's the world we have to live in.

A selector in Enlive is almost identical to the corresponding CSS selector. Generally speaking you just need to wrap the CSS selector inside a CLJ vector and prefix it with the colon : (i.e. keywordize the CSS selectors).

For example, if you want to select a tag with an id="quantity" attribute, you need to write [:#quantity] which corresponds to the #quantity CSS selector.

NOTE 3: I strongly suggest that you read the Enlive syntax for selectors so you have at least a basic understanding of the subject.

But what about the transformation functions? Enlive offers a lot of them but this is not a tutorial on Enlive. I'm going to use the only function we need in our context: the (set-attr &kvs) function. It accepts keyword/value pairs, where the keywords are the names of the attributes you want to set. In our sample, the only attribute we are going to set is the value attribute of each input field. So let's start by adding to the shopping.clj deftemplate call both the selector clause and the trasformation function for each input field:

(ns modern-cljs.templates.shopping
  (:require [net.cgrand.enlive-html :refer [deftemplate set-attr]]))

(deftemplate shopping "shopping.html"
  [quantity price tax discount]
  [:#quantity] (set-attr :value quantity)
  [:#price] (set-attr :value price)
  [:#tax] (set-attr :value tax)
  [:#discount] (set-attr :value discount))

With JS disabled reload the shopping URI and change the values of the input fields of the Shopping Calculator form. By clicking the Calculate button you'll receive the form with the same values you previously typed in. So far, so good.

It's now time to calculate the result for the Total field of the shoppingForm.

Evolve by refactoring the code

By continuously improving the design of code, we make it easier and easier to work with. This is in sharp contrast to what typically happens: little refactoring and a great deal of attention paid to expediently adding new features. If you get into the hygienic habit of refactoring continuously, you'll find that it is easier to extend and maintain code. - Joshua Kerievsky, Refactoring to Patterns

In Tutorial 9 - Introducing Ajax we defined the remote calculate function by calling the defremote macro. The defremote call implicitly defines a function with the same name as the remote function; that's good, since we hate any kind of code duplication. We could immediately use it to calculate the result of the Shopping Calculator by just parsing the [quantity price tax discount] passed to the deftemplate call.

But wait a minute. We've already parsed those arguments on the CLJS side of the calculate function and we don't want to parse them again. To reach this DRY objective we need to refactor the code by moving the parsing code of the fields values from the client side to the server side.

Let's take a look at the CLJS shopping.cljs file where we defined the client side calculate function.

(defn calculate [evt]
  (let [quantity    (read-string (value (by-id "quantity")))
        price       (read-string (value (by-id "price")))
        tax         (read-string (value (by-id "tax")))
        discount    (read-string (value (by-id "discount"))) ]
    (remote-callback :calculate
                     [quantity price tax discount]
                     #(set-value! (by-id "total") (.toFixed % 2)))
    (prevent-default evt)))

As you can see, to parse the input string, we used the read-string function from the cljs.reader lib of CLJS.

ATTENTION NOTE: parsing a string coming from user input with read-string is very dangerous from a security point of view. Here we are not taking care of this issue, but you should. One possible solution is to use regular expressions.

Let's now refactor the calculate functions we defined in both CLJS and CLJ source files. Open the shopping.cljs file under the src/cljs/modern_cljs directory and modify it by removing the cljs.reader from the namespace requirements and by removing the calls to read-string in the calculate function definition as follows:

(ns modern-cljs.shopping
  (:require-macros [hiccups.core :refer [html]])
  (:require [domina.core :refer [by-id value by-class set-value! append! destroy!]]
            [domina.events :refer [listen! prevent-default]]
            [hiccups.runtime :as hiccupsrt]
            [shoreleave.remotes.http-rpc :refer [remote-callback]]))

(defn calculate [evt]
  (let [quantity   (value (by-id "quantity"))
        price      (value (by-id "price"))
        tax        (value (by-id "tax"))
        discount   (value (by-id "discount")) ]
    (remote-callback :calculate
                     [quantity price tax discount]
                     #(set-value! (by-id "total") (.toFixed % 2)))
    (prevent-default evt)))

;;; the rest as before

Now the :calculate remote-callback function accepts strings as arguments and we should refactor it as well. Open the remotes.clj file under the src/clj/modern_cljs directory and modify the calculate function as follows:

(defremote calculate [quantity price tax discount]
  (-> (* (read-string quantity) (read-string price))
      (* (+ 1 (/ (read-string tax) 100)))
      (- (read-string discount))))

We're now ready to add the calculate function to the template definition in the shopping.clj file under the src/clj/modern_clj/templates directory.

Open and modify the above file as follows:

(ns modern-cljs.templates.shopping
  (:require [net.cgrand.enlive-html :refer [deftemplate set-attr]]
            [modern-cljs.remotes :refer [calculate]]))

(deftemplate shopping "shopping.html"
  [quantity price tax discount]
  [:#quantity] (set-attr :value quantity)
  [:#price] (set-attr :value price)
  [:#tax] (set-attr :value tax)
  [:#discount] (set-attr :value discount)
  [:#total] (set-attr :value
                      (format "%.2f" (double (calculate quantity price tax discount)))))

NOTE 4: We added the format call to format the Total value with two digits after the decimal point. Note that we cast the calculate result to double.

Assuming that you have your IFDE running, as soon as you save the file you'll receive an error.

FIAT - Fix It Again Tony

Too bad. We just met a cyclic namespaces dependency problem. Cyclic namespace dependencies are not allowed in CLJ so you need to refactor the code.

The modern-cljs.templates.shopping namespace now requires the modern-cljs.remotes namespace to access the calculate remote function. In turn, the modern-cljs.remotes namespace requires the modern-cljs.core namespace to access the handler function. In turn, the modern-cljs.core namespace requires the modern.cljs.templates.shopping namespace to access the shopping function implicitly defined by the deftemplate macro call.

modern-cljs.templates.shopping -> modern-cljs.remotes
-> modern-cljs.core -> modern-cljs.templates.shopping

Our solution is simple enough. Remove the modern-cljs.core reference from the modern-cljs.remotes namespace declaration. There, we only referenced the handler symbol from the modern-cljs.core namespace in the app definition. By moving the app definition to the modern-cljs.core namespace we should be able to resolve the cyclic issue.

We have just met another case in which it is easier to restart the IFDE than it is to alter its runtime environment. This is because we now have to substitute the :handler value in the serve task of the build.boot from modern-cljs.remotes/app to modern-cljs.core/app.

So, stop any boot related process and modify both the remotes.clj and the build.boot files.

Following is the modified content of the remotes.clj file where we have removed both the reference to the modern-cljs.core namespace and the app symbol definition.

(ns modern-cljs.remotes
  (:require [modern-cljs.login.validators :as v]
            [shoreleave.middleware.rpc :refer [defremote]]))

(defremote calculate [quantity price tax discount]
  (-> (* (read-string quantity) (read-string price))
      (* (+ 1 (/ (read-string tax) 100)))
      (- (read-string discount))))

(defremote email-domain-errors [email]
  (v/email-domain-errors email))

NOTE 5: We also removed the references to the wrap-rpc symbol and to site since they are not used anymore by any functions defined in this file.

Next, we need to add the app symbol definition in the modern-cljs.core namespace and add both the shoreleave.middleware.rpc and compojure.handler requirements to be able to reference wrap-rpc and site symbols in the app definition. Following is the modified content of the core.clj file.

(ns modern-cljs.core
  (:require [compojure.core :refer [defroutes GET POST]]
            [compojure.route :refer [not-found files resources]]
            [compojure.handler :refer [site]]
            [modern-cljs.login :refer [authenticate-user]]
            [modern-cljs.templates.shopping :refer [shopping]]
            [shoreleave.middleware.rpc :refer [wrap-rpc]]))

(defroutes handler
  (GET "/" [] "Hello from Compojure!")  ;; for testing only
  (files "/" {:root "target"})          ;; to serve static resources
  (POST "/login" [email password] (authenticate-user email password))
  (POST "/shopping" [quantity price tax discount]
        (shopping quantity price tax discount))
  (resources "/" {:root "target"})      ;; to serve anything else
  (not-found "Page Not Found"))         ;; page not found

(def app
  (-> (var handler)
      (wrap-rpc)
      (site)))

Last, but not least, we have to modify the build.boot file to update the namespace of the app symbol in the :handler section of the serve task.

(deftask dev
  "Launch immediate feedback dev environment"
  []
  (comp
   (serve :handler 'modern-cljs.core/app               ;; new ring handler
          :resource-root "target"                      ;; root classpath
          :reload true)                                ;; reload ns
   (watch)
   (reload)
   (cljs-repl) ;; before cljs
   (cljs)
   (target :dir #{"target"})))

We are now ready to rebuild and run everything.

Start the IFDE

boot dev
...
Elapsed time: 19.405 sec

Visit the shopping URI and play with the form by enabling and disabling the JavaScript engine of your browser. Everything should work as expected in both the scenarios.

When you're done, kill the boot process and reset your git repository.

git reset --hard

Next Step - Tutorial 14: It's better to be safe than sorry (Part 2)

In the next tutorial, after having added the validators for the shoppingForm, we're going to introduce unit testing.

License

Copyright © Mimmo Cosenza, 2012-2016. Released under the Eclipse Public License, the same as Clojure.