Refactoring Legacy CFML with Approval Tests: Part II

In part I of this blog series we walked through using TestBox-Snapshots to create some approval tests for refactoring a legacy CFML application. In this post, we will focus on setting our application up to use the ColdBox MVC framework. Once it is setup we can start using the MVC pattern to separate concerns. We start by installing ColdBox.

Setup

box install coldbox

Now we will use the ColdBox scaffolding tools to get the ColdBox simple application template. I don’t want to overwrite my existing application so I install it in a temporary directory so that I can get the things I want from it.

box mkdir .tmp
box coldbox create app directory=".tmp" name="ToDo" skeleton="simple" init=false installColdBox=false

Application.cfc

First I will replace my existing Application.cfc file with the one from the template.

box mv Application.cfc _Application.cfc
box cp .tmp/Application.cfc Application.cfc

We then add our datasource and table initialization query back into it. Here is a link to the full modified Application.cfc file.

Layouts

We will then create an empty default layout for our application.

box mkdir layouts
box touch layouts/Main.cfm --open

Then add the following content to layouts/Main.cfm.

<cfoutput>#renderView()#</cfoutput>

Views

Now we create our views directory and move our index.cfm file into it. We do this because ColdBox has implicit views which do not require a handler. Eventually we will refactor this but for now, we will just move it. We also move the includes file into our views directory as well. In a real life legacy application, you should be able to move your entire app under views to get it up and running. See this blog post for more details.

box mkdir views
box mv index.cfm views/index.cfm
box mkdir views/includes
box cp includes/form.cfm views/includes/form.cfm

Once we move our application files into views we can copy the empty index.cfm file into the site root.

box cp .tmp/index.cfm index.cfm

Config

We will copy the entire config directory from the ColdBox template.

box cp .tmp/config config

Handlers

To start we will not need handlers but the template comes with some application lifecycle handlers so we will copy that directory as well.

box cp .tmp/handlers handlers

We should no longer need the ColdBox template that we downloaded to a temp directory so we can delete it.

box rm .tmp --force --recurse

Routing

In order to make our URLs behave the same, we need using URL rewrites and setup some application routes. So we will need to start the CommandBox server with rewrites enabled.

box server start rewritesEnable=true

In config/Routes.cfm we add a route for the default page. According to this blog post, you will need a route for any page in your application’s root but anything in a sub-directory should work using the implicit views.

// Your Application Routes
addRoute(pattern="/", view="index");
addRoute(pattern=":handler/:action?");

Approval Tests

The application should now be running under ColdBox. You will notice however that if you run the approval tests now they fail. This is because our approval tests are not setup to use ColdBox. We could change our test to use cfhttp to get the page content or we can change our test so it is a ColdBox integration test.

To convert our test to a ColdBox integration test we need to do a few things. First, our test needs to inherit from the ColdBox BaseTestCase instead of the TestBox BaseSpec.

So this…

component extends="testbox.system.BaseSpec"{

Should change to this.

component extends="coldbox.system.testing.BaseTestCase" appMapping="/root" {

We also need to call super.beforeAll() and super.afterAll() like so.

// executes before all suites+specs in the run() method
function beforeAll(){
	super.beforeAll();
	addMatchers( "testbox-snapshots.SnapshotMatchers" );
}

// executes after all suites+specs in the run() method
function afterAll(){
	super.afterAll();
}

Then at the start of our beforeEach method we need to call setup() to setup the ColdBox request for each test.

beforeEach(function( currentSpec ){

	setup();
	...

In our tests, instead of using cfinclude we will use the execute method to execute the ColdBox event and get the rendered HTML from that.

it( "should display a list of to-do items", function(){

	var event = execute( event="index", renderResults=true );

	content = event.getValue( name="cbox_rendered_content" );

	expect(content).toMatchSnapshot();
});

After converted all the tests they should pass when ran. Here is the fully coverted test suite.

Now that our application is running with Coldbox we can start using the MVC design pattern to refactor our application and continue to use the approval tests as a guide.