2014-10-06 13 views
8

Ich habe Om/React-Komponenten entwickelt, aber ich fühle mich sehr unwohl, wenn ich meine Entwicklung nicht mit Komponententests durchführen kann. Ich habe versucht, mein clojurescript-Projekt einzurichten, um Komponententests für diese Komponenten auszuführen, und habe den Punkt erreicht, an dem ich Komponententests schreiben und meine Komponenten instanziieren kann. Was ich vermisse, ist die Fähigkeit sicherzustellen, dass meine Komponenten auf bestimmte Ereignisse, z. onChange, damit ich Benutzereingaben simulieren kann.Wie man Om/React-Komponenten richtig (Einheit) testet?

Hier ist mein Testcode:

(defn simulate-click-event 
    "From https://github.com/levand/domina/blob/master/test/cljs/domina/test.cljs" 
    [el] 
    (let [document (.-document js/window)] 
    (cond 
    (.-click el) (.click el) 
    (.-createEvent document) (let [e (.createEvent document "MouseEvents")] 
           (.initMouseEvent e "click" true true 
               js/window 0 0 0 0 0 
               false false false false 0 nil) 
           (.dispatchEvent el e)) 
    :default (throw "Unable to simulate click event")))) 

(defn simulate-change-event 
    "From https://github.com/levand/domina/blob/master/test/cljs/domina/test.cljs" 
    [el] 
    (let [document (.-document js/window)] 
    (cond 
    (.-onChange el) (do (print "firing on change on " el) (.onChange el)) 
    (.-createEvent document) (let [e (.createEvent document "HTMLEvents")] 
           (print "firing " e " on change on " (.-id el)) 
           (.initEvent e "change" true true) 
           (.dispatchEvent el e)) 
    :default (throw "Unable to simulate change event")))) 

(def sink 
    "contains a channel that receives messages along with notification type" 
    (chan)) 

;; see http://yobriefca.se/blog/2014/06/04/publish-and-subscribe-with-core-dot-asyncs-pub-and-sub/ 
(def source 
    (pub sink #(:topic %))) 

(defn change-field! 
    [id value] 
    (let [el (sel1 (keyword (str "#" id)))] 
    (dommy/set-value! el value) 
    (simulate-change-event el) 
    )) 

(deftest ^:async password-confirmation 
    (testing "do not submit if passwords are not equal" 
    (let [subscription (chan)] 
     (sub source :user-registration subscription) 
     (om/root 
     (partial u/registration-view source sink) 
     nil 
     {:target (sel1 :#view)}) 

     (go 
     (let [m (<! subscription)] 
     (is (= :error (:state m))) 
     (done) 
     )) 

     (change-field! "userRequestedEmail" "[email protected]") 
     (change-field! "userRequestedPassword" "secret") 
     (change-field! "confirmPassword"  "nosecret") 

     (simulate-click-event (sel1 :#submitRegistration)) 
    ))) 

Dieser Test läuft, aber schlägt fehl, da die change-field! Funktion tatsächlich nicht den Zustand der Komponente ändern. Hier ist (ein Teil) der Code der Komponente (vergeben Vervielfältigung ...):

(defn registration-view 
    "Registration form for users. 

    Submitting form triggers a request to server" 
    [source sink _ owner] 
    (reify 

    om/IInitState 
    (init-state [_] 
       {:userRequestedEmail "" 
       :userRequestedPassword "" 
       :confirmPassword ""} 
       ) 

    om/IRenderState 
    (render-state 
    [this state] 
    (dom/fieldset 
     nil 
     (dom/legend nil "User Registration") 
     (dom/div #js { :className "pure-control-group" } 

       (dom/label #js { :for "userRequestedEmail" } "EMail") 
       (dom/input #js { :id "userRequestedEmail" :type "text" :placeholder "Enter an e-mail" 
           :value (:userRequestedEmail state) 
           :onChange #(om/set-state! owner :userRequestedEmail (.. % -target -value))})) 

     (dom/div #js { :className "pure-control-group" } 
       (dom/label #js { :for "userRequestedPassword" } "Password") 
       (dom/input #js { :id "userRequestedPassword" :type "password" :placeholder "Enter password" 
           :value (:userRequestedPassword state) 
           :onChange #(om/set-state! owner :userRequestedPassword (.. % -target -value))})) 

     (dom/div #js { :className "pure-control-group" } 
       (dom/label #js { :for "confirmPassword" } "") 
       (dom/input #js { :id "confirmPassword" :type "password" :placeholder "Confirm password" 
           :value (:confirmPassword state) 
           :onChange #(om/set-state! owner :confirmPassword (.. % -target -value))})) 


     (dom/button #js {:type "submit" 
         :id "submitRegistration" 
         :className "pure-button pure-button-primary" 
         :onClick #(submit-registration state sink)} 
        "Register"))))) 

Was ich, indem sie Spuren in den Tests sehen kann, ist, dass der Zustand der Komponente nicht aktualisiert wird, wenn ich den Abzug Ereignis, obwohl es korrekt ausgelöst wird. Ich vermute, dass dies mit der Funktionsweise von Om/React zu tun hat, die DOM-Komponenten umschließt, aber nicht sicher ist, wie damit umzugehen ist.

+0

Nur um sicher zu machen: Ihre getestet Komponente om überhaupt (auch nur ‚im Gedächtnis‘?) Gemacht Können Sie das DOM bestätigen Elemente werden tatsächlich erstellt und der onChange-Handler ist angefügt? – phtrivier

+0

Ja. Das 'on click'-Ereignis wird ausgelöst, ich kann die Nachricht durch den core.async-Kanal sehen: Das ist was 'submit-registration' tut, sendet das Ergebnis eines xhrio-Aufrufs an den' source'-Kanal, der dann von der '(go ...)' Schleife innerhalb des Tests. – insitu

+0

@insitu Vielleicht ein anderer Ansatz wird helfen. Ich teste reagierende Komponenten mit mochify und ich fügte ein Beispiel zu mochify's Wiki-Seite hinzu: https://github.com/mantoni/mochify.js/wiki/Testing-a-ReactJS-Component-with-Mochify –

Antwort

1

Sie können Ereignisse in Ihren Komponenten mit ReactTestUtils aus den Reaktivbibliotheken nachbilden. Ich bin mit Mokka und so etwas wie dies zu tun Änderungsereignisse testen:

var comp = ReactTestUtils.renderIntoDocument(<Component />); 
var changingElement = ReactTestUtils.findRenderedDOMComponentWithClass(comp, 'el-class'); 
it ('calls myChangeMethod on change', function() { 
    ReactTestUtils.Simulate.change(changingElement); 
    assert(comp.myChangeEventMethod.called, true); 
}