Lately I have been getting a lot of questions on how to test User Interface(UI) code. People always claim that UI testing is very hard or even that it is not possible. I think that with the right kind of design, UI testing is just as easy as testing any other piece of code. Let me show you how I do unit-testing of UI in Adobe FLEX which uses ActionScript as its programming language.
Lets say we wish to test a common UI component such as a login page.
The important thing is to separate the graphical UI from the control logic and data. This can be achieved with the standard Model View Controller design pattern. Where Model is the data (username/password), View is the visual components (TextField, Button) and Controller is what glues the pieces into an interactive UI (What happens when I click Login button.) However, from testing point of view there is one important caveat which can not be broken! The source code dependence must be expressed in following order.
View -> Controller -> Model
In other words Controller and Model can never know about the View! Neither direct or transitive dependencies are allowed. (i.e. Controller knows about X and X knows about View is just as bad as Controller knows about View). Similarly the Controller knows about the Model but model does not know about the Controller (although that requirement is not as strict.) Often times I merge the Model and the Controller into a single class if I don’t expect any reuse of the Model. Such as in this case of a login page.
Lets start with a Controller/Model
package example.flextesting { [Bindable] public class LoginPage { public var username:String; public var password:String; public var showError:Boolean; public var authenticator:Function; public function login():void { showError = authenticator(username, password); } } }
Notice how closely the Controller mimics the actual UI. Each entry field gets a field, each UI state (showError) gets its field as well, and finally each action gets a method. Also notice the [Bindable] annotation which allows any class to listen to modification in object state. In out case we want the View to be able to listen to state changes of the Controller without the controller explicitly knowing about the View.
Now that we have a Controller lets look at the View:
<?xml version="1.0" encoding="utf-8"?> <mx:VBox xmlns:mx="http://www.adobe.com/2006/mxml" xmlns:flextesting="example.flextesting.*"> <flextesting:LoginPage id="controller"/> <mx:Form> <mx:FormItem label="Username:"> <mx:TextInput text="{controller.username}" change="controller.username = event.currentTarget.text"/> </mx:FormItem> <mx:FormItem label="Password:"> <mx:TextInput text="{controller.password}" change="controller.password = event.currentTarget.text" displayAsPassword="true"/> </mx:FormItem> <mx:FormItem label="Label"> <mx:HBox> <mx:Button label="Login" click="controller.login()"/> <mx:Label text="Login Failed" visible="{controller.showError}" color="#FF0000" fontWeight="bold"/> </mx:HBox> </mx:FormItem> </mx:Form> </mx:VBox>
Notice that the View has a direct access to the controller Also notice that all of the TextInputs are bound to the corresponding fields on the Controller (In ActionScript the {controller.username} mans that the value is bound at runtime to the destination. Meaning any changes in username/password will be reflected in the text fields.) Because ActionScript data binding is not bidirectional, we also register change events on the TextInput which which copy any changes in the UI back to the controller. We then bind the “Login” button to the Controller login() method. Finally we bind the error message “Login Failed” visibility to controler.showError.
All this binding achieves that the Controller is fully separated from the view. So from now we can forget about the view and just worry about testing the Controller. Now many people will argue that I still can have errors in the wiring / binding process. True, but from my personal experience most errors are in the logic not in the boring wiring code. The wiring code either is broken and it does not compile or it compiles and chances are it is right. By ignoring the wiring and the graphical portion of the UI It is unlikely that I have left too many bugs in the code. Also even if I test the View I still don’t know if it “looks right” which only a human can do. So I simply take a very pragmatic approach and draw the line at the View. I get 90% of benefits with very little cost. Turns out that there are scenario based frameworks out there which will allow you to write test with full View code coverage, but those are not unit-tests and hence I will not go into them here.
As you may have guessed the Controller will mimic the View very closely. This is actually very desirable as you don’t want to have “impendence mismatch” when trying to do the wiring. Any “impendence mismatch” will result in marshaling code which may turn your simple binding problem into a hidden controller and hence moves logic from the true home of Controller into the bindings/view which is undesirable.
Lets see how the above helps testing as this very simple test shows…
package example.flextesting { import flexunit.framework.TestCase; public class LoginPageTest extends TestCase { public function testLogin():void { var loginPage:LoginPage = new LoginPage(); loginPage.username = "user"; loginPage.password = "pass"; var log:String; loginPage.authenticator = function(u,p) { log = u + "/" + p; return true; } assertEquals("user/pass", log); assertTrue(loginPage.showError); } } }
Notice the since the Controller mimics the View the Controller forms a kind of a domain-specific-language (DSL) which is actually very useful in scripting serration tests and also in understanding what the test is doing.
Finally lets look at one last thing, and that is how the whole thing is wired up. Your controller will need to collaborate with your application service objects. This implies that the control is dependency-injection (DI) heavy and should therefore be injected into the view. As usual you will need a single top level factory which instantiates all of the services, Controllers, Views and service and then inject all of the references into appropriate places. Here is the FLEX equivalent of the “main method”.
<?xml version="1.0" encoding="utf-8"?> <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" xmlns:flextesting="example.flextesting.*"> <mx:Script> <![CDATA[ function authenticator(username:String, password:String):Boolean { return username != password; } ]]> </mx:Script> <flextesting:LoginView> <flextesting:controller> <flextesting:LoginPage authenticator="{authenticator}"/> </flextesting:controller> </flextesting:LoginView> </mx:Application>
Without going too much into the details of FLEX the tag element is equivalent to the new operator. So
<flextesting:LoginPage authenticator="{authenticator}"/>
is same as
var loginPage:LoginPage = new LoginPage(); loginPage.authenticator = authenticator;
Therefore the above example is the place where all of the components get instantiated and the references get passed around to appropriate objects. (Good old dependency-injection).
– Misko Hevery
14 responses so far ↓
Your images are missing in this blog post. Can you re-publish it with the images intact? This is some good material.
Fixed the images. Thanks for pointing it out…
In the LoginPageTest, won’t you need to call the controller’s method:
public function login():void
Without calling void() how will the showError be populated?
Also, would you use a different design in reality for setting the error message?
You have:
showError = authenticator(username, password);
But instead would you catch an authentication exception, and set the error based on that. If the authenticator returns true when it succeeds, the showError will equal true, which is non-sensical.
Power of data bindings.
Calling login() will change the controller.showError to true. Notice the code below.
color="#FF0000" fontWeight="bold">
The visible property is data bound to controller.showError which will result in the error message displayed.
See the login() method can take a while to decided if the login was successful an so it can not return true or false. The showError field can be changed some time later.
Love those data bindings!
I did not get it about Model-View-Contoller. You say Controller shouldn’t be dependent on View, but aren’t contollers exist exactly for briding views and data, and interfacing them to rest of the system? Programs I worked with had dependencies like this:
ViewModel
How does view know what visual elements it’s to change when model changes? Via observing model’s events? Or how?
Sorry, my ascii drawing got screwed but html preprocessor or something. I drawed this:
View ← Controller → Model
@CJ
I am sure in the bast you have seen the dependencies go all different ways. But I bet you those ones are hard to test.
A View is rendering, that is hard to test. Unlikely that you will be able to assert anything interesting. Not to mention since it is rendering it needs some graphics context and so it will not be a unit test.
A Controller has lots of interesting code/behavior in it which you want to test. But if controller knows about the view than Controller is now hard to test since dependencies are transitive. That is why we need to break that dependency.
Now you are right, we need to be able to communicate data from Controller back to UI. So to do that we use data bindings. (Or events). This way we can easily unit test the controller and don’t have to pay the expensive view set up.
Now many systems have this backwards, or even have circular dependencies. My guess is that those systems are also not easy to test.
Constructor Injection vs. Setter Injection // Feb 19, 2009 at 11:24 am
[...] I was building a Flex application and using the Model-View-Controller. Flex XML markup requires that components must have no argument constructors, therefore I was [...]
hi misko,can u please suggest any tools where in a tester can identify the color, fonts, fonts size on a UI.
This is a good presentation of a controller communicating with a view using data binding. But I have two concerns. One thing I do not like about data binding is that it can lead to subtle bugs regarding time. That is, when you have multiple data items that work together, you want to be sure that all of them pertain to the same entity. For example, that they all belong to the current customer, not a previous customer that the user clicked through. The other concern I have is with complex user interfaces. If there is some complex manipulation of the user interface, that requires more that just accessing the text property of a UI component, such as creating additional components on the fly, the programmer would have to decide how to allocate the code between the view and the controller. That might make for some difficult decisions. It might also tempt the programmer to have the controller access the View. In any case, thank you for the presentation.
I think in the test you’re missing :
loginPage.login();
before
assertEquals(“user/pass”, log);
Constructor Injection vs. Setter Injection | Abhijit Gaikwad // Jun 29, 2012 at 1:57 am
[...] I was building a Flex application and using the Model-View-Controller. Flex XML markup requires that components must have no argument constructors, therefore I was [...]
can you write test cases for above manually.
Thanks
Hareesh
Constructor Injection vs. Setter Injection | TechInterviewPrep // Mar 9, 2013 at 4:01 pm
[...] I was building a Flex application and using the Model-View-Controller. Flex XML markup requires that components must have no argument constructors, therefore I was [...]
Leave a Comment