MVC review: how to write code and tests
Some web2.0 guys might not understand quite well what each part of the “ehm-veh-ceh” pattern should and should not do. They should forget about “fat model and thin controller” misconcept. Today we try to help them. (Code should be light in either case.)
First of all, a simple example about “why it matters”:
A music player contains two volume controls and a sound stream. One control is a slider, another one is a “mute” button. Sound stream has an API with “volume”, “bass”, “track number” properties. If you try to modify sound stream directy within button and slider event callbacks, you will face a problem of UI consistency: when mute button is clicked, slider should be disabled or slide down. Ad hoc attempt is to bloat your callbacks with bunch of if-then-else branches checking all the possible cases in the whole UI in each callback!
A better way is to setup a single controller for all the volume controls with two actions: “triggerMute” and “setVolume(level)”. Then, subscribe each button to a volume property of the sound model, so that slider moves a handle according to the current volume level and mute button looks “unpressed” when the level is > 0. The final step is a controller which simply sets the volume level or triggers it. The only visible if-then-else branch here is the triggerMute method which should check the current volume level before changing it. However, this is an application-specific behavior which is out of scope of MVC pattern.
Generally speaking, your application is about “entities”. Given that:
1. Models are about state of entities. They describe consistent state and referential integrity within the whole system. Model must guarantee that any transition will result into consistent state. Model defines validations and constraints, but doesn’t define how transitions are performed (e.g. doesn’t check permissions). In case of database record, it should allow to change any values in any way, but ensures the consistency of the state just before transaction is committed. Perfect Model does not have “action” methods, but reader/writer accessors only.
When you write tests for a model (you write them, don’t you?) you should not describe an “operational process” with a model, but rather check that any possible update yields a valid state or appropriate errors are raised.
2. Controllers, on the other hand, are about actions. Controller does not bother about consistency of state (it may be stateless) but it specifies the control flow. That is, checks permissions, creates, deletes and moves entities around.
In a test for a controller you should describe initial state of the system, perform an action and then check the resulting state.
3. Views are about representation of the entities. View does not modify the entity, but it accesses its data, observes changes made to a model and generates events for a controller. When you test a view, you should define a set of entities (the input) and check their representation (the output).
The most simple and powerful MVC picture looks like that:
M -> V
V -> C
C -> M
View observes the state (M.) and updates itself when state is changed.
Controller observes the view and performs appropriate actions when some controls are triggered.
Bonus track. How to write perfect tests for a music player “volume feature”?
1. Model test verifies that volume level could not be below zero or over 9000.
2. Controller test checks several values for the “setVolume” method and two cases for the “triggerMute” method: with level = 0 and level = 1. Before the test, volume level is set to some value and after the action it is checked.
3. View test should check that the slider and the button generate events for the controller and observe events generated by the model.
