Neptune 1.0 Beta 3 Documentation: Tests

Neptune Information

Download from RIA Forge

Tests

One of the advantages of the way that Neptune uses data definitions, is that (outside of the data definitions) no additional code is needed in the model to manage the CRUD work. This is nice not only because it reduces the amount of work to create an application or the amount of code needed to run one, but because it helps identify business logic.

If you need to add any code to your model (and remember the view should only display and the controller should only translate data between the view and the model), then you are probably adding business logic to your application. If you are adding business logic to your application, then you need to have tests to verify your business logic is working as desired.

So, before you add any code to your model, write tests for it.

Writing the Tests

First write down the business rules that you are trying to enforce. Once you know those, write a test stub for each business rule. The generator can write the stubs for you and RecordsTester.cfc can make writing the tests themselves easier.

Although unit testing is beyond the scope of this documentation, the basics are that you should write code that will verify that the business rule is being met and call a "fail" method (passing a string indicating what failed) if it doesn't. RecordsTester.cfc is itself based on (and requires) MXUnit.

You can add a user interface for your tests by using the "Rules" program that can be found on the RulesMgr GitHub page.

Example

For example, let's say that our client for Contact-O-Matic wants to make sure that any contact entered that has the name "Smithe" is saved with the name "Smith" instead (that spelling is just a pet peave of his, I guess).

We would first create a "tests" folder in our Contact-O-Matic application and then put a TestContactOMatic.cfc in it (all of this is just convention).

Here is TestContactOMatic.cfc with just a stub for the unit test:

<cfcomponent displayname="Contact-O-Matic" extends="com.sebtools.RecordsTester">

<cffunction name="setUp" access="public" returntype="void" output="no">
	
	<cfset loadExternalVars("ContactOMatic")>
	<cfset loadExternalVars(varlist="NoticeMgr",skipmissing=true)>
	
</cffunction>

<cffunction name="shouldSmitheBeRenamedSmith" access="public" returntype="void" output="no"
	hint="Any contact with the name 'Smithe' should have that name changed to 'Smith'."
	mxunit:transaction="rollback"
>
	
	<cfset fail("Test not yet implemented")>
	
</cffunction>

</cfcomponent>

Now that we have our test stub, let's write the actual unit test itself. What we really need to do is save a record where the contact has a name of "Smithe" and then verify that it was changed to "Smith". So, here is the new "doshouldSmitheBeRenamedSmith" method:

<cffunction name="shouldSmitheBeRenamedSmith" access="public" returntype="void" output="no"
	hint="Any contact with the name 'Smithe' should have that name changed to 'Smith'."
	mxunit:transaction="rollback"
>
	
	<cfset var sContact = {ContactName="James Smithe"}>
	<cfset var contactid = saveTestRecord(variables.ContactOMatic.Contacts,sContact)>
	<cfset var qContact = variables.ContactOMatric.Contacts.getContact(contactid)>
	
	<cfif qContact.ContactName NEQ "James Smith">
		<cfset fail("The name 'Smithe' was not renamed to 'Smith'.")>
	</cfif>
	
</cffunction>

The "saveTestRecord" takes a "Records" component (like "Contacts") and saves random data. If a structure is passed in for the data, then any keys in the structure will over-ride the randomly generated data.

We could also take advantage of the the "assertEquals" from MXUnit:

<cffunction name="shouldSmitheBeRenamedSmith" access="public" returntype="void" output="no"
	hint="Any contact with the name 'Smithe' should have that name changed to 'Smith'."
	mxunit:transaction="rollback"
>
	
	<cfset var sContact = {ContactName="James Smithe"}>
	<cfset var contactid = saveTestRecord(variables.ContactOMatic.Contacts,sContact)>
	<cfset var qContact = variables.ContactOMatric.Contacts.getContact(contactid)>
	
	<cfset assertEquals(qContact.ContactName,"James Smith","The name 'Smithe' was not renamed to 'Smith'.")
	
</cffunction>

Without the Database

In many cases, these tests can be written without actually saving any data at all. This is convenient because it means that the tests will work even on a simulated database. It also makes the tests faster and removes the need for the "mxunit:transaction" attribute (which has yet to be rolled into the official MXUnit repository).

This takes advantage of the fact that Records components have a built in validate method that always runs before any save method. This is where this kind of functionality should be written anyway. We'll discuss more of how it works as we go.

Here is the revised test:

<cffunction name="shouldSmitheBeRenamedSmith" access="public" returntype="void" output="no"
	hint="Any contact with the name 'Smithe' should have that name changed to 'Smith'."
>
	
	<cfset var sContact = variables.ContactOMatic.Contacts.validateContact(ContactName="James Smithe")>
	
	<cfset assertEquals(sContact.ContactName,"James Smith","The name 'Smithe' was not renamed to 'Smith'.")
	
</cffunction>

Any time the "saveContact" method of Contacts is called, the validateContact method will be called (technically, it is called from within the "saveRecord" method which is called by "saveContact" by default). So, that would be the place to put this kind of clean up code.

The "validateContact" method (like any other "validate" method) will return a structure.

Now that we have written our test, we can go ahead and write our custom logic.

Next: Custom Logic