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.
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....
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.
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:
Here is the entire test component:
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 componenttestobj=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); }); }); } }
Comments
Post a Comment