RSpec: Simple Tests to Learn its Behavior
Few people would argue that making it a point to ensure some level of testing is in place to verify that the functionality built works as expected is a bad practice. Size and complexity will typically dictate the testing philosophy used on a project. I tend to balance the effort required to build and maintain the automated tests versus the effort required to manually retest a module when a change is introduced. Fortunately, RSpec has a lot of power available for enabling reuse, readability, and, most importantly, brevity. All good things for developers trying to meet tight deadlines. Generally, I attempt to keep it as simple as possible by carefully orchestrating the basic let()/it() combined with good use of context to build most of my tests. Granted, when I wrote my first test, I was definitely fighting the expected philosophy of RSpec. Once I started to understand the behavior of each of its components, the pieces started falling into place and devising examples became a lot easier. Here’s a few of trials I used to gain more clarity around how the different components work together.
Using context to control scope
Context creates scope for the examples such that let() definition in other contexts can’t interact with the current context:
describe "let() scope" do
# Visible to both context blocks
let(:var1) { 3 }
context "block 1" do
# Only visible to this block
let(:var2) { 5 }
# Error
specify { var3.should eq(7) }
# Ok
specify { var2.should eq(5) }
specify { var1.should eq(3) }
end
context "block 2" do
# Only visible to this block
let(:var3) { 7 }
# Error
specify { var2.should eq(5) }
# Ok
specify { var3.should eq(7) }
specify { var1.should eq(3) }
end
end
Running this test results in these failures:
1) let() scope block 1
Failure/Error: specify { var3.should eq(7) }
NameError:
undefined local variable or method `var3' for #<RSpec::Core::ExampleGroup::Nested_1::Nested_1:0xb6b78ef8>
# ./spec/test_spec.rb:14
2) let() scope block 2
Failure/Error: specify { var2.should eq(5) }
NameError:
undefined local variable or method `var2' for #<RSpec::Core::ExampleGroup::Nested_1::Nested_2:0xb6b763d8>
# ./spec/test_spec.rb:25
Redefine let() value
Define the value once and redefine it as necessary. Context blocks can work with the default or override it to something different for the given context without affecting another context:
describe "let() redefined" do
let(:var1) { 3 }
context "block 1" do
let(:var1) { 5 }
specify { var1.should eq(5) }
end
context "block 2" do
let(:var1) { 7 }
specify { var1.should eq(7) }
end
# Unaffected by block 1 and 2
context "block 3" do
specify { var1.should eq(3) }
end
end
Cascading usage of let() values
Use previous let() values to define other values in a given context. This enables reuse and readability throughout the different contexts and examples:
describe "let() reuse" do
let(:var1) { 3 }
context "block 1" do
let(:var2) { var1 + 5 }
specify { var2.should eq(8) }
end
context "block 2" do
let(:var2) { var1 + 7 }
specify { var2.should eq(10) }
end
end
Be aware of recursion
Values created by let() are really just methods which will be called when referenced. Trying to define the same value again by referencing itself will cause a recursion error:
describe "let() recusive" do
let(:var1) { 3 }
context "block 1" do
# Fail
let(:var1) { var1 + 5 }
specify { var1.should eq(8) }
end
end
You’ll see something like this. In more complex tests, these might be hard to find. Until it completely clicked what let() was really doing, I didn’t entirely realize what I had done:
3) let() recusive block 1
Failure/Error: let(:var1) { var1 + 5 }
SystemStackError:
stack level too deep
Obviously, these are quite simple. However, I keep a little scratch pad of simple examples that I can tinker with and try out ideas to see the behavior before trying it out in a larger, more complex test script. Once you start adding in a database environment, application state, etc, it becomes difficult to stay true to the simple principles available in the test suite. Sometimes, going back to basics allows you to see things more clearly and apply them in other contexts.
Filtering in Backbone: Let the Model Do the Work
I had previously posted about different approaches to filtering Backbone collections. However, as I spent time trying to solve my problem I kept finding that the solutions did not offer the flexibility I needed. I liked the overall approach but felt that it might be better to decouple the collections of filters from the application of the filters. The solution I arrived at was to move the decision of whether a filter matched into the model. A controller pattern would tie the collection process to the application process to manage the different models and collections involved in the process.
Below is a simple example that illustrates the main points of the pattern:
- The model contains all the logic to decide if it matches the provided selection criteria
- There is a source collection that contains all the models that can be selected
- A second collection is reset with the array of matching models returned by the source’s filter function
- A controller manages the interactions between the different components
var Team = Backbone.Model.extend({
defaults: {
name: '',
score: 0
},
selector: null,
match: function ( select ) {
this.selector = select;
return this.get('score') > select.score;
}
});
var Teams = = Backbone.Collection.extend({
model: Team
});
var selector = { score: 50 },
source = new Teams([
{ name: 'Rockets', score: 27 },
{ name: 'Colts', score: 67 },
{ name: 'Giants', score: 55 }
]),
selected = new Teams(),
matcher = function( model ) { return model.match(selector); };
selected.reset( source.filter(matcher) );
What I like about this approach is that the process of applying the filter is very simple and repeatable. The complexity can be deferred to the model which, as long as it understands the contents of the filter arguments, can be designed to test if it matches the selection criteria in whatever way is applicable to the target solution. It also leverages all the built-in functionality already available in Backbone which also reduces the amount of required code to build it. Finally, it has the added benefit of pushing the selector down to each model. This can come in handy later if you’d like to validate a model still matches when its updated or use it to format the attributes during rendering (ie highlighting matching text in a regex search).
Sometimes a bigger, more functional example helps solidify the idea. I built a tiny contact lookup list which has an input box to search all the available attributes in the model. You can filter by name, organization, phone, and email. The definition of the model has slightly more complex matching logic that will look for the provided search text in each of the four columns:
App.Models.Contact = Backbone.Model.extend({
defaults: {
display_name: '',
organization: '',
primary_email: '',
primary_phone: ''
},
selector: null,
match: function ( select ) {
this.selector = select;
return (
select.search_text.length == 0 ||
( select.search_text.length > 0 &&
_.chain(
_.values(
_.pick(this.attributes, 'display_name', 'organization', 'primary_phone', 'primary_email')
)
)
.any(
function( s ) {
return s.toLowerCase().indexOf(select.search_text.toLowerCase()) > -1;
}
)
.value()
)
);
}
});
Since this is searching several different columns, I’d like to highlight the search text where it was found in each item so the user can quickly see why it is included in the results. Since the formatting is injecting HTML content into the data, it doesn’t really belong in the model. However, since the model recorded the selector used to find it, the view that renders each item can use it to apply the formatting:
App.Views.ContactItem = Backbone.View.extend({
template: null,
tagName: 'ul',
className: 'ui-list-item',
initialize: function ( options ) {
this.template = _.template($('#contact-item').html());
},
render: function () {
this.$el.html(this.template( this.formattedJSON() ));
this._super();
return this;
},
formattedJSON: function () {
var item = this.model,
select = item.selector,
data = item.toJSON(),
check = new RegExp( $.ui.autocomplete.escapeRegex(select.search_text), 'i' );;
if ( select.search_text.length > 0 ) {
for ( var d in data ) {
if ( data[d] && check.test(data[d]) ) {
data[d] = data[d].replace(
new RegExp(
'(?![^&;]+;)(?!<[^<>]*)(' +
$.ui.autocomplete.escapeRegex(select.search_text) +
')(?![^<>]*>)(?![^&;]+;)', "gi"),
'<span class="ui-search-highlight">$1</span>');
}
}
}
return data;
}
});
Now the whole thing can be wired up inside a controller object that will coordinated the views, models, and collections required to make it all work:
App.Controller = Controller.extend({
filter: null,
source: null,
selected: null,
container: null,
layoutView: null,
filterForm: null,
resultList: null,
initialize: function ( options ) {
var options = options || {};
this.container = options.container || 'body';
},
start: function () {
this.source = new App.Collections.Contacts( localData );
this.selected = new App.Collections.Contacts();
this.filter = new Backbone.Model({ search_text: '' });
this.layoutView = new App.Views.ContactLayout();
this.filterForm = new App.Views.ContactFilter({ model: this.filter });
this.resultList = new App.Views.ContactList({ collection: this.selected });
$(this.container).append( this.layoutView.render().$el );
this.layoutView.$('.ui-list-options').append( this.filterForm.render().$el );
this.layoutView.$('.ui-list-content').append( this.resultList.render().$el );
this.listenTo(this.filter, 'change', this.applyFilters);
},
applyFilters: function () {
var selector = this.filter.toJSON();
this.selected.reset( this.source.filter(function ( model ) {
return model.match( selector );
}));
}
});
This controller is bit simplified but you can see the different elements of the pattern by keeping it flatten out. The filter is now a simple model that can be bound to an input box. By leveraging some autobinding magic, we can listen for changes to the filter model to trigger the actual filtering on the list.
This example only works with a small set of local data. It can be extended to query a remote data source. In that scenario, the source collection would be setup to fetch from the server under certain conditions to retrieve a larger dataset that can further be filtered locally. You can attach events to the source’s request and sync events to show/hide a wait spinner and rebuild the selected collection after the fetch is complete. The only other consideration worth noting is that the model assumes it will be a member in one filter collection. This might pose a problem is you are using multiple filtering collections that are not mutually exclusive. If that happens, you’d either need to record the context of the filter (which collection it came from) or find an alternative approach.
Patterns for Filtered Collections in Backbone
Most likely, you’ll have a need to filter the data in a collection. Although there are built-in filtering functions in the Backbone Collection object, they don’t affect the actual collection instance – just return an array of models. If you’re trying to filter the collection to cause a view to render the filtered set, you’ll need to put that array of models in another collection. It doesn’t make sense to reset the collection you filtered, you might want to clear the filter and go back to the original data set.
I’ve been playing with both Backbone Query and Backbone Subset to figure out what approach might work best for building a consistent, repeatable pattern for this problem.
Subset provides an all-in-one solution with the ability to create a Subset object from a source collection, add filters, and bind a view to the filtered collection available in the Subset model. Since the filters are a collection as well, you can bind them into a view and build a UI around them so your list of filtered items will update automatically.
Query doesn’t provide the same framework but does offer a more powerful filtering engine. Combining Subset and Query together would create a fairly powerful tool for extracting subsets of data from a primary source collection.
However, one size doesn’t always fit all. I had a case where the filters I wanted to allow entered from the UI did not map nicely on to the collection in Subset and the filtering logic needed to be implemented as a custom function passed to the Backbone Collection filter() function.
I do think the overall pattern defined by the Subset solution makes sense:
- Start with a container model, add a source collection, filter collection, and results collection
- Instance the container model with the source collection
- Bind the container to a view that will manage the filter and list views
- Have the container view instance and render the filter/list views binding in the corresponding collections from the container model
- Add appropriate handler logic to listen to filter changes, apply transforms, and filter the source to reset the result collection
The actual querying/filtering implementation depends on you needs. The setup is basically the same. Now you maintain one instance of all your models but can render multiple views of those models based on the application requirements.
Reviewing CSS Layout Techniques for Controling Content Size to Fit the Page
Sometimes going back to basics is not a bad thing. Generally, I learn something new every time I start over. In this case, I decided to spend some time looking at how to improve/simplify setting up the basic layout I might use to build an application. When I think about the problems I need to consider when creating my layout, I generally can break them down into these basic issues:
- Creating various “regions” to hold content that are either fixed or resize to fit the remaining space
- Keeping all the content inside the viewable browser window bounds. Any scrolling will be dealt with inside the different content “regions”
- Ensuring the desired layout works well with widget libraries like jQuery UI
I used the term “region” to be as generic as possible. These can mean columns, headers, rows, panels – anything that holds some functional part of the application. That can translate into menus, tables, toolboxes, or various other widgets. For this discussion, I chose a basic 3-column layout where the middle column expands to fill the remaining space not used by the two fixed outside columns. The whole layout should also fill the entire available space on the page (both height and width). I’m going to start by solving the columns layout, move to the height issue, and then review how adding several widgets might require additional tweaking.
Creating a 3-Column Layout
I’ve found several approaches for creating multi-column layouts with both fixed and fluid columns. For a 3-column setup, the most common are two fixed column (left and right) with a fluid middle column.
Most references use floating elements but I also saw solutions for absolute positioned elements. In the end, I chose to use absolute positioning. Others may be more comfortable with floats, however, I prefer avoiding them. The setup for the columns is fairly simple – set the left and right to the fixed amount and then set the left/right margin of the middle element to match. Then use absolute positioning to line each element up across the page. When you resize the page, the left/right stay constant and the middle will automatically fill the remaining space.
<div class="col-wrapper">
<div class="col-left">
<div class="col-content">
</div>
</div>
<div class="col-mid">
<div class="col-content">
</div>
</div>
<div class="col-right">
<div class="col-content">
</div>
</div>
</div>
.col-wrapper {
position: relative;
width: 100%;
font-size: 1.2em;
}
.col-left, .col-mid, .col-right {
position: absolute;
}
.col-left {
left: 0;
width: 300px;
background-color: yellow;
}
.col-mid {
left: 0;
margin-left: 300px;
margin-right: 300px;
background-color: lightblue;
}
.col-right {
right: 0;
width: 300px;
background-color: pink;
}
.col-content {
margin: 0.8em;
}
Notice that .col-mid defines a margin the same size as the right/left width. The result looks like this page. That pattern will work well for 2 and 3-column layouts. Moving to four or more might pose a bigger challenge.
Filling the Available Height
So far, the content in the columns will flow down the page and the browser will scroll the whole view. In an application, this probably is not desirable since one region may contain a menu you want to remain in view when the user is scrolling through a list of data. This means you need to ensure you contain the content to the height and width of the browser. The width is generally easy to manage, block elements naturally fill the width of their parent – its the height that always causes problems. As much as I’d like to keep this a pure CSS solution, I’ve found that using a tiny bit of Javascript is really the best way to deal with the height. Not only can you set it when the page finishes loading, you can also reset it every time the browser window is resized:
$(function() {
$(window).on('resize', function () {
$('.col-wrapper').outerHeight($(window).outerHeight());
}).trigger('resize');
});
This takes care of the outer wrapper for the columns, however, the individual columns are not sized to fill that height. The simple solution seems to just make the height equal 100%. Which will work until you add any margin or padding on the element that you set this way. Since the columns are just going to define the location of other elements and not anything more, I can safely use height: 100% in my style. However, the content inside those columns also needs to fill the space and have margin to create some white space. If I apply the 100% height rule to those element, I get this resulting layout. The goal is to get something that looks like this:
I stumbled upon a site with various HTML/CSS layout patterns. The one I found that solved the problem is named “Stretched” and used absolutely positioned elements with the left/right/top/bottom styles set to force the element to fill in the available space with the margin included in the calculation. This approach avoids the problem with the inner element overrunning the bounds of the parent element because the height/width is 100% and the margin/padding causes the element to push outside the parent. Applying that concept to my layout example fixes the problem and the content fits perfectly with the desired margins.
Adding Widgets to the Design
Now that we have the two main pieces to our layout, let’s try building something a little more useful. I took the same 3-column layout and added jQuery UI widgets to the different elements. One of the things to consider when doing this is that most of the widgets will add certain styles to the target elements (or even wrap them). These styles need to be accounted for in the layout so everything looks lined up and uncluttered (no extra borders, etc).
Before diving into specific issues related to the widgets, I factored out the important parts of the layout so it was reusable across anything I needed to use it with. The columns where in pretty good shape already, but the stretching styles can be separated into one class that can mixed with other class styles as needed:
/* Setup container to fill column area and not overflow the bounds */
.fill-box {
position: absolute;
height: auto;
top: 0;
bottom: 0;
left: 0;
right: 0;
overflow: hidden;
}
As long as there is a relative parent, this will cause the target element to completely fill the available space created by the parent element. Other styles can define any margin/padding/border and not leave the bounding box.
Next we can add specific styles for each column. The left column will hold an Accordion widget which will have Draggable elements in each grouping. The middle will hold a Sortable widget that will also accept the Draggable elements, and the right column will hold some additional content to fill the space.
/* Define specific styles for each column container */
.toolbox {
margin: 5px;
margin-top: 3px;
padding-bottom: 1px;
}
.sortable {
margin: 5px;
border: 1px solid #aaa;
overflow-y: auto;
}
.content {
margin: 5px;
border: 1px solid #aaa;
padding: 5px;
overflow-y: auto;
}
The markup has the normal column elements plus a container for the widgets. The sortable and content area do not need any other wrappers, however, the accordion does to ensure it sizes correctly. Without the extra DIV, it was cut off at the bottom.
<div class="column-wrapper">
<div class="column-left">
<div class="fill-box toolbox">
<div>
/* Need to wrap the accordion so it sizes correctly */
<h3>Stuff A</h3>
<div>
/* DIVs for the Draggable widget */
</div>
<h3>Stuff B</h3>
<div>
/* DIVs for the Draggable widget */
</div>
<h3>Stuff C</h3>
<div>
/* DIVs for the Draggable widget */
</div>
<div>
</div>
</div>
<div class="column-middle">
<div class="fill-box sortable ui-corner-all">
/* DIVs for the Sortable widget */
</div>
</div>
<div class="column-right">
<div class="fill-box content ui-corner-all">
/* Paragraphs of text */
</div>
</div>
</div>
Now, the code to initialize each widget may need to be modified to handle the sizing we would like. The Sortable doesn’t need any special consideration, however, both the Draggable and Accordion do require a few adjustments to account for the width/height we are targeting.
First, the accordion widget has a heightStyle which can be set to “fill” so the widget will match the height of its parent. This calculation is only done one upon initialization of the widget so any subsequent screen resizes will not cause the widget to fill the height properly. Since there is already a handler in place for the window resize, the accordion can be refreshed in that handler to resize it as well.
/* Track the window height and set the outer column wrapper to match */
$(window).on('resize', function () {
$('.column-wrapper').outerHeight($(window).outerHeight());
/* Make the accordion recalc the height */
$('.toolbox > div').filter('.ui-accordion').accordion( 'refresh' );
}).trigger('resize');
$('.toolbox')
.accordion({ heightStyle: 'fill' });
Next, the draggable needs some tweaking to move correctly from the accordion widget to the sortable widget. Whenever the source element is cloned to be dragged to the sortable, it will fill the width of the parent it will be appended to (it needs to be in the column-wrapper so it doesn’t get lost from the overflow). Since the helper can take a function, we can clone the element and set its width to match so it retains its size during the drag:
$('.draggable')
.draggable({
connectToSortable: '.sortable',
helper: function() {
return $(this).clone().outerWidth($(this).outerWidth());
},
appendTo: '.column-wrapper'
}).disableSelection();
This approach provides a reusable pattern for crafting sections of a page that require filling specific dimensions. Its repeatable from the top of the page all the way down to images and buttons. If you’ve been struggling trying to keep portions of your page fluid without having to manage it with code or hard-coded sizing rules, then some of the ideas here might make things a little easier.






