Search code examples
unit-testingclojureclojurescriptom

Testing components with async api calls by mocking the request


I'm still in a learning phase for Cljs and Om. I'm looking into writing comopnent test. Some components have cljs-http calls to an API I created. When testing, I do not want those API calls to actually send the request, so I'm looking into mocking the request and returning a fixture. Here's an example component I have:

(defn async-component [data owner]
  (reify
    IWillMount
    (will-mount [_]
      (let [resp (go ((<! (async-call "/") :body))]
        (om/update! data [:objects] resp)))
    IRender
    (render [_]
      [:ul
        (om/build-all item-component data)])))

(defn async-call [path]
  (http/get path {:keywordize-keys true}))

Please don't mind if the code is actually syntactically correct, I'm just showing the gist of it.

What I now want to do is test this async-component and the API call to see if it will render the fixture that I mock the request with. How is this done? I know cljs.test has the async block to test async code with, but all example show it testing actual code blocks that only have a go in it, not in a larger context.


Solution

  • Here is a way you might use mocking to test your component:

    (deftest test-async-component
      (cljs.test/async done
        (with-redefs
          [async-call (fn [path]
                        (let [mock-ch (async/chan 1)
                              fixture-data {:body {:fixture-with path :and "foobar"}})]
                          (async/put! mock-ch fixture-data)
                          mock-ch)]
          ; At this point we successfully mocked out our data source (the API call)
          ; the only task that remains is to render our Om component into DOM and inspect it.
          ; As this task requires utility fns I will reuse the ones in this blog post:
          ; http://lab.brightnorth.co.uk/2015/01/27/unit-and-browser-testing-om-clojurescript-applications/
    
          (let [c (new-container!)
                initial-data {:objects [{:initial-object 42}]}]
            ; This will mount and render your component into the DOM residing in c.
            (om/root async-component initial-data {:target c})
    
            (testing "fixture data gets put into the DOM"
              (is (= "foobar" (text (sel1 c :ul)))))
    
            ; You can add more tests in this manner, then finally call 'done'.
            (done)))))
    

    The steps taken in the above code in English:

    1. Write async-call's mock fn that returns a channel (the same interface as the original one) prefilled with fixture data.
    2. Mock out the original fn (you need to refer it or fully qualify the ns).
    3. Create a new virtual DOM for unit testing purposes.
    4. Specify the mock data that doesn't come from the API, if any.
    5. Render your component into DOM (this will call async-call when om/will-mount runs, taking the fixture-data off the chan).
    6. Observe DOM contents.