Skip to main content

Mocking Methods - FINALLY!

This is part of a series of posts on Testing and Mocking.

The next (and final) challenge for this evening was to adapt the query method to retrieve the query from another method in the same component rather than the component's property. This is almost identical to the previous except without using a mocking tool we can't easily change a method call or what a function returns at run time. This is our first case of using a tool like MockBox.

public function mathFromFunctionQuery(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.getQ()); <---different part. from method, not property.
    local.res=local.qc.execute().getresult();
    return local.res.answer[1];
}

private function getQ(){
    return this.getdataQ();
}

In order to mock this, we need to somehow overwrite the getQ() method at test time in a way that doesn't do any actual code damage to the existing CFC. It was then that an epiphany of the blindingly obvious came to me and I realized that....

when you're using mocks to test a function, 
you're not mocking the function, 
you're mocking everything else around it.

Ok, as epiphanies go, it wasn't exactly Newton's Apple or the Theory of Displacement but it was my own little "Eureka" moment. Suddenly the docs for MockBox made much more sense. So what does it allow us to do? Simply put, (among other things) MockBox can create a replica of a componet which we can then adapt to our own purposes or monitor to see how it runs. That's it, but that's alot. The best way to demo that is to walk through it. What are we trying to accomplish? We are trying to control what is returned when our mathFromFunctionQuery calls the getQ() function. That's it. Notice, I didn't say "we're trying to control what getQ returns to mathFromFunctionQuery". That's a big difference. We actually don't care if the getQ() method even exists other than that an error isn't thrown when mathFromFunctionQuery() calls it. 

Here is the test that arose, line by line:


This create a new instance of MockBox
mockBox = new testbox.system.MockBox();

This creates a new "mock" of the vanillaCFC component
testobj=createmock("testmods.first.second.third.vanillaCFC");


If we dump out testobj, we'll see a completely new component with a number of additional methods which all have a purpose but - and this is key - it also has all of the functions which were originally in vanillaCFC.cfc exactly as they were in the original.

We create the query which we want to use as our source. 
local.q=queryNew("numA,numB,answer","int,int,int",[{"numA":8,"numB":9,"answer":89}]);

note: I know 8 * 9 isn't 89 but I differentiated to make sure I was seeing the new query and not anything I'd created before.

Here is the magic line:
testobj.$("getQ",local.q);




What this says is "in testobj (our mock), whenever something calls the getQ method, return local.q". In doing this, we can control what gets used as the source query in the function we care about, which is the mathFromFunctionQuery() method.


This gets the return value to be evaluated.
testme=testobj.mathFromFunctionQuery(8,9);


Here is the entire test component:

component extends="testbox.system.BaseSpec"{
   
/*********************************** LIFE CYCLE Methods ***********************************/
   // executes before all suites+specs in the run() method   
   function beforeAll(){
      mockBox = new testbox.system.MockBox();

      testobj=createmock("testmods.first.second.third.vanillaCFC");
      local.q=queryNew("numA,numB,answer","int,int,int",[{"numA":8,"numB":9,"answer":89}]);
      testobj.$("getQ",local.q);
      testme=testobj.mathFromFunctionQuery(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 89", function(){
               expect( testme ).toBe(89);
         });
         
      });
      
   }
   
}

Conclusion

This makes sense. I originally thought it meant I had alot of work to do recreating what I thought were my unit tests. Then I realized that that wasn't exactly the case. It just means that I have a number of integration tests already written (which might need to be tweaked) and I need to actually create my unit tests in the first place. 

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...

As the Dev Ops Manager, I need to start planning our CI/CD release process

Part of the CI/CD Development Series Once we have our Deployment Diagram designed, we need to figure out out to get from here to there. If the end point is the appropriate server environment, the starting point is the developer with his/her hand on the keyboard. These steps take place on a variety of machines, within various process and can changes based on what files are checked in or not. At the moment, we've only created the early basics as seen below. The Beginning of our Deployment Activity Chart Even though there is quite a long way to go there are some elements which we have already been determined. For example We have determined the broad strokes of our technology stack. This is going to be React on the front end and ColdFusion on the server side. We have determined that we are going to be using linting on both the CF and React paths. CFLint for the CF and ESLint for the Javascript We have determined that we are going to be formatters - CFFormat for CF and...

As the Dev Ops Coordinator, I need to set up our git repo into several branches with the appropriate permissions for each one

Part of the CI/CD Development Series The core of every CI/CD process is the code repository whether it be Git, Mercurial, SVN or whatever. The general idea is that it allows multiple developers (or whomever) to access your code in the appropriate way in the appropriate level. This can either be the ability for anyone to pull an open source project but not write to the repo directly or full access to a developer on your team to create branches, push to master or anything that needs doing. For our project, we're using git although the hosting provider was up for discussion between Github, Bitbucket by Atlassian or CodeCommit on AWS. We decided to go with AWS for two reasons. 1. We are going use other tools in AWS as part of the build so we decided to keep it all together. 2. We needed to solidify the ins and outs of using IAM for the process. Basic Steps Create the Repo Create the branches we need Use IAM to apply the appropriate permissions to each branch and to set up ...