Blog

Gavin Pickin

March 18, 2016

Spread the word


Share your thoughts

Are you writing APIs? Going to start writing APIs? Now is the time to start testing. Do it early, and often, and reap the benefits as the project grows. TestBox, the defacto CFML test suite, can help your test your API, whether it’s CFML or not.

I will assume you have the basic TextBox installation setup.
 

#box install testbox
    

You have a folder structure like this
/tests/
/tests/runner.cfm
/tests/specs/

 

Runner.cfm could look something like this

<cfsetting showDebugOutput="false">
<!--- Executes all tests in the 'specs' folder with simple reporter by default --->
<cfparam name="url.reporter"         default="simple">
<cfparam name="url.directory"         default="tests.specs">
<cfparam name="url.recurse"         default="true" type="boolean">
<cfparam name="url.bundles"         default="">
<cfparam name="url.labels"             default="">
<!--- Include the TestBox HTML Runner --->
<cfinclude template="/testbox/system/runners/HTMLRunner.cfm" >

 

Let’s assume your API is written in CFML, lets look at how you write those tests.
Let’s add a new spec file for our CFML CFC tests, in this file.
/tests/specs/cfcTest.cfc

Note: The API we're testing in here is not a real world example. In a true REST Api you wouldn't have a function called login. You would pass an authentication token with each request, but for simple example, I am using login to show the inputs, and expected outcomes. 

We’re going to add a single describe blog, with three tests ( it blocks ).
In these tests, we’re going to initialize a CFC, and then expect the return of a CFC function call to be a specific result.

Our first test, we’re creating a userServiceRemote cfc.
When we call userServiceRemote.login() with the email “gavin@gavin.com” and the password “topsecret”, the struct returned will have a key ‘result’, and we expect that result to be 400, for a bad login.

In our third test, again we create the CFC, we call login, but this time we’re calling with “gavin@gavin.co.nz” and the password “topsecret”, this time, with correct credentials, the struct with key result returns a 200, for success.

component extends="testbox.system.BaseSpec" {       

    function run() {            
        describe("userService API Login", function(){            
            it( "will error with incorrect login", function(){    
                var oTest = new cfcs.userServiceRemote();
                expect( oTest.login( 'gavin@gavin.com', 'topsecret').result ).toBe('400');
            });            
            it( "will error with incorrect password", function(){
                var oTest = new cfcs.userServiceRemote();
                expect( oTest.login( 'gavin@gavin.co.nz', 'notsecret').result ).toBe('400');
            });
            it( "will success with correct login", function(){
                var oTest = new cfcs.userServiceRemote();
                expect( oTest.login( 'gavin@gavin.co.nz', 'topsecret').result ).toBe('200');            
            });
        });
    }

}

Ok great, simple TestBox tests, using CFML CFC creation, and calling functions, Unit test style.
These tests run fast, you can test all your code, inside and out.


What if we want to test the Request Headers, Verbs, or our API isn’t even written in CFML. TestBox can do that easily, using CFHTTP, instead of Unit Tests, you’re running Integration tests. Here is how we would write the same tests, hitting the API endpoints.

In this example, instead of creating an instance of a CFC, we build a URL, and we call it with CFHTTP. The example below uses a get, for simplicity, but the same can apply to other verbs.
We call the endpoint, passing the email address, and the password, and store the result in the “cfhttpResponse” variable. Once we deserialize the json for the cfhttpResponse.filecontent, we check the “result” key, and see if the return code is 400.

 

it( "will error with incorrect login", function(){
    var email = "gavin@gavin.com";
    var password = "topsecret";
    var cfhttpResponse = "";
    http url="http://www.testableapi.local.com:8504/user/login/email/#email#/password/#password#" result="cfhttpResponse";
    expect( DeserializeJSON(cfhttpResponse.filecontent).result ).toBe('400');
});

The full test spec might look like this.

 

component extends="testbox.system.BaseSpec" {
    function run() {
        describe("userService API Login", function(){
            it( "will error with incorrect login", function(){
                    var email = "gavin@gavin.com";
                    var password = "topsecret";
                    var cfhttpResponse = "";
                    http url="http://www.testableapi.local.com:8504/cfcs/userServiceRemote.cfc?method=login&email=#email#&password=#password#" result="cfhttpResponse";
                    expect( DeserializeJSON(cfhttpResponse.filecontent).result ).toBe('400');
            });
            it( "will error with incorrect password", function(){
                    var email = "gavin@gavin.co.nz";
                    var password = "notsecret";
                    var cfhttpResponse = "";
                    http url="http://www.testableapi.local.com:8504/cfcs/userServiceRemote.cfc?method=login&email=#email#&password=#password#" result="cfhttpResponse";
                    expect( DeserializeJSON(cfhttpResponse.filecontent).result ).toBe('400');
            });
            it( "will success with correct login", function(){
                    var email = "gavin@gavin.co.nz";
                    var password = "topsecret";
                    var cfhttpResponse = "";
                    http url="http://www.testableapi.local.com:8504/cfcs/userServiceRemote.cfc?method=login&email=#email#&password=#password#" result="cfhttpResponse";
                    expect( DeserializeJSON(cfhttpResponse.filecontent).result ).toBe('200');
            });
        });  
    }
}


These tests are much slower, since you’re firing off the http calls, but they test your full integration. Testing like this also gives you access to the full response object, so your tests have the ability to check headers, call with different verbs, and so much more than you would just doing Unit Tests.

Add Your Comment

(2)

Mar 14, 2017 11:05:06 UTC

by dan fredericks

Gavin, how would i call a https api path? is it the same call? maybe it is because I am using vpn, but I can't get the http url="" result="" to work, it gives me a 500 error when i try to call my https://.... site. http url="https://myserverapipath/?action=pathTocfcFile" result="cfhttpResponse"; I must just be doing something slightly wrong...maybe this will help others... thanks

Jul 17, 2018 10:24:18 UTC

by Keen Haynes

Having a problem with second example. If i call endpoint using url http://myapiserver//rest/restMobile/scUser/602157/.json it returns the following raw response - {"RESPONSESTATUSCODE":200,"ALLOWNONSTOCK":1}. If I try the following based on your example ``` component extends="testbox.system.BaseSpec" { function run() { describe("uuserService API supply center user", function(){ it( "will error with incorrect user id", function(){ var user_key = "602157123"; var cfhttpResponse = ""; http url="http://myrestserver/rest/restMobile/scUser/#user_key#" result="cfhttpResponse"; expect( DeserializeJSON(cfhttpResponse.filecontent).responsestatuscode ).toBe('400'); }); }); } } ``` my application.log file shows the following error - Invalid construct: Either argument or name is missing.When using named parameters to a function, each parameter must have a name.

The CFML compiler was processing:

  • An expression beginning with describe, on line 4, column 9.This message is usually caused by a problem in the expressions structure.
  • A script statement beginning with describe on line 4, column 9.
  • A script statement beginning with function on line 2, column 5.
The specific sequence of files included or processed is: D:\web\scms\testbox\test-runner\index.cfm, line: 4 and the TestBox Global Runner shows a blank results screen. Could you please give me a nudge in the right direction....

Recent Entries

Into the Box - Updates as of June 27th, 2022

Into the Box - Updates as of June 27th, 2022

Into the Box Super Early Bird ticket pricing is over, but you can still get the Early Bird. Last week we had more episodes of the Modernize or Die Podcast - Conference Edition for ITB 2022 and announced the first 2 groups of Pre-Conference Track for Into the Box.

Gavin Pickin
Gavin Pickin
June 27, 2022
Ortus Content Digest for week of June 24th

Ortus Content Digest for week of June 24th

What has Ortus been publishing this week? We have the CFML News Podcast, more ITB Workshop Podcasts, some CFCasts and YouTube Videos, lots of Ortus and ITB Blog Posts. We have a lot more planned for next week as well

Gavin Pickin
Gavin Pickin
June 24, 2022
Online CF Meeting Series on Code Reuse, 3rd Party Libraries and Package Management

Online CF Meeting Series on Code Reuse, 3rd Party Libraries and Package Management

Gavin has presented several webinars on the Online ColdFusion Meetup with Charlie Arehart. This series was inspired by many questions from customers and community members, wanting to learn more about code reuse, when should they write their own modules, when should they use java or coldfusion/cfml packages, and why should people use CommandBox and ForgeBox vs manually handling your 3rd party and shared libraries. This series tries to go back to basics, and explain pros and cons of each approach, hopefully shedding light and demystifying 3rd party libraries, package management, CommandBox endpoint installations, and ForgeBox being the home of all CFML, not a tool for box only products and tools. Check out this blog post for all of these sessions, and links on how to view them on YouTube.

Gavin Pickin
Gavin Pickin
June 23, 2022