Skip to main content

Mocking a Query

This is the third (or fourth) in a series about recent discoveries in testing and mocking. This challenge was to accept two numbers and use them to query a database to get the result. Is this a real world example? Maybe not for math but obviously we use DBs all the time so that part is real.

The function starts off simple enough.

public function mathFromQuery(numeric numA,numeric numB){



}



We're still accepting the two numbers. then we run into the crux of the issue which is how are we to we provide the results of a query without actually calling the DB which may or may not exist in our testing environment. This will not work:

public function mathFromQuery(numeric numA,numeric numB){


    local.qs=new query(datasource=blah,sql="select * from blah where blah blah blah");
}

Since the query originates from the function we have no way of controlling its contents except to provide a db. The only way that anything can exists from the outside of a function is to be passed in and that's only done through
a) a parameter in a function (like numA and numB are) or
b) as a property of the CFC itself.

I chose this latter option and adapted the CFC to look like this:

component accessors="true"{
    property name="dataQ" type="query";

 
   public function mathFromQuery(numeric numA,numeric numB){

    }
}

From there it was (almost) trivial to set up the rest of the method but how would this property get set in production? Either at instantiation or via dependency injection. This might not be the more real world approach to a query but this is all about exploring techniques which we can mix and match later.

public function mathFromQuery(numeric numA,numeric numB){
    local.qc=new query(dbtype="query",
        sql="select answer from sourceQuery where numA=:numA and numB=:numB");
    local.qc.addParam(name="numA",value=arguments.numA);
    local.qc.addParam(name="numB",value=arguments.numB);
    local.qc.setAttributes(sourceQuery=this.getDataQ());
    local.res=local.qc.execute().getresult();
    return local.res.answer[1];
}

Some key points here are in the "setAttributes()" line where we set a "sourceQuery" attribute. Since a query of queries refers to the name of the source query and not the query itself, this was the way to do that. Thanks to Chris Tierny (@CFJSGeekand this post from 2011.

Creating the Test

Here are some of the key points of this. 

  1. We have yet to use Mockbox in any of this and this wasn't an exception
  2. We instantiate the object and then use queryNew to make the data we need. This does present one difficulty going forward which is going to be making sure that this schema matches the actual one in production (since everyone uses a db to do math, right?)
  3. We can use the queryNew to create multiple scenarios. We have the main one in  the beforeAll() but notice how we can manipulate the query in the second it() just to make sure we are using the submitted query to get our result.

component extends="testbox.system.BaseSpec"{
   
/*********************************** LIFE CYCLE Methods ***********************************/
   // executes before all suites+specs in the run() method   function beforeAll(){
      testobj=createObject("component","testmods.first.second.third.vanillaCFC");
      local.q=queryNew("numA,numB,answer","int,int,int",[{"numA":8,"numB":9,"answer":72}]);
      testobj.setDataQ(q);
      testme=testobj.mathFromQuery(8,9);
   }

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

/*********************************** BDD SUITES ***********************************/
   function run(){
   
      describe( "My test Suite", function(){

         it( "be a number", function(){
               expect( testme ).toBeTypeOf('numeric');
         });

         it( "should be boo", function(){
            local.q=queryNew("numA,numB,answer","int,int,int",[{"numA":8,"numB":9,"answer":"boo"}]);
            testobj.setDataQ(q);
            testme=testobj.mathFromQuery(8,9);
               expect( testme ).toBe('boo');
         });

         
      });
      
   }
}

Result

So far, this mocking and testing seems to be less about using a new tool (MockBox)and more about designing your methods and CFCs to be testable using techniques we already know.

Comments

Popular posts from this blog

Creating Stories and Tasks in Jira: Personas and our Software Development Team

Part of the CI/CD Development Series The next step is developing who is on our hypothetical development team. Given that it has a React front end and ColdFusion as the Server Side language, I came up with the following personas, all of which have their own needs and considerations for our development environment. I've listed all the jobs that need doing, not the people involved since, even on a small team or a team of one, these "hats" are all worn by someone, even if it's the same person. Personas for our Project Dev Ops Coordinator - The person responsible for smooth and accurate deployments CF Developer - The person responsible for the API and fulfillment code development and maintenance. React Developer - The person responsible for the front end development Database Coordinator - The person responsible for the schema, data, up time and, presumably the testing databases used by the developers. Lead Developer - The person responsible for coordinat...

CF: Scripting amidst the tags

Recently I had to do a quick "utility" page where I needed to see how many files from a directory listing had been recorded into a database. I've been writing about 98% of my CF code in script syntax but, since it was quick and easy, I did this quickly in tags because it was outputting directly to the browser. In doing so, I made an interesting discovery in that, when you use closures, even in tag based pages, you can write cfscript. Here's the example Get the directory listing:  < cfset alljs = directoryList(expandpath( '/src' ), true , "path" , "*.js" )> Get the database listings and convert it to an array < cfquery name = "alljsQ" datasource = "blah" > select * from sitefiles where filename like '%.js%' </ cfquery > < cfset recordedFiles = valuelist(alljsQ.filename).listToArray()> Use a filter function to weed out the files I'd already recorded < cfset missingFiles = alljs.fi...

More on Site Architecture / CI/CD / and Repos

We're starting to move from the high level overview and more into the details of our project.  Architecture We established that we using React as our front end technology and ColdFusion as our server side. At a more granular level, we'll be using React with Redux for our front end app and we want our server side to be not just "ajax" enabled but a true REST API. To this end, we're going to incorporate Coldbox from Ortus Solutions. Repos These two code bases have different needs, and possibly locations, when they are deployed. As a result, we're going to have two repositories: one for the React and another for the API. This will allow us to keep our code separated and cleaner all the way through to deployment.  For each of these repos, we'll follow the procedure we laid out previously with the MASTER branch being used to deploy to production and a DEVELOPMENT branch being used for active work which is then fed into the Master branch via pull requests.  Test...