Thursday, 26 November 2015

Clojure - How to replace current time creation?

Recently, I've begun my adventure with Clojure programming language. As a result I've decided to share gathered knowledge and my opinion, in this and in few upcoming posts.

The problem
I had to implement algorithm that depends on the current date. Core information for this algorithm is number of days between current date and some date in the future, expressed in days.
Therefore, there is a call somewhere in the code:
(. java.time.LocalDate now)
For the tests to be stable, I had to make sure that this call always return the same day.

Approach 1
I've decided to extract creation of the current date functionality to the function:
(defn now-date [] (. java.time.LocalDate now))
During tests I've declared different function:
(defn fixed-date [] (. java.time.LocalDate of 2015 01 28))
Passing function that creates a current date, solved the problem. It worked great, but it had the following disadvantages:
- Passing to algorithm function that creates current time.
- Using java notation (with dot) in Clojure.

Approach 2
Having a function, that returns a current time, I've decided to find a way of overwriting its definition in tests. I've found out that there is operation called with-redefs-fn, which allows re-defining the function temporarily in the local context. Having defined fixed-date function, block of code looks like this:
 (deftest my-test
  (with-redefs-fn {#'fixture/now-date fixed-date}
    #(do     
      (testing "should work method a"        
        (let [result (fixture/do-stuff 30)]
          (is (.equals 8.22M result))
          ))
      ;More tests
          )))

fixture/now-date is a reference to function that I wanted to replace. This time I was amazed by language possibilities. But there was one more problem to solve. I did not want to use java notation.

Approach 3
There is a library called Clj-time. It wraps Joda Time library and makes Clojure code more friendly. I wanted to hold on Java 8 library, but I did not see any alternatives. 

So I replaced (. java.time.LocalDate now) to (t/now) and also creation of fixed dates, and then I came up with an idea.

Approach 4
Maybe should I replace the Clj-time itself? My production code will be simpler and the test code will be simpler too!
 (deftest my-test
  (with-redefs-fn {#'t/now #(t/date-time 2015 11 19)}
    #(do     
      (testing "should work method a"        
         (let [result (fixture/do-stuff 30 1000)]
          (is (.equals 8.22M result))
          ))
      ;More tests 
          )))
This is my final solution. I am still impressed how easily it that can be done.

I use Clojure for a week. If you have any other ideas how to solve this problem comment, let me know.

No comments:

Post a Comment