Best way to inject a mock to dynamic methods?

118 views
Skip to first unread message

Brian G

unread,
Aug 4, 2015, 9:02:17 PM8/4/15
to mxunit

I've got a problem that I don't have a clean solution for and could use some pointers on a better way to structure my code or test.  I have a method that dynamically creates and returns an object, getBalance().  getBalance() is called by the method I want to test, settle()

I can create a mock for getBalance() to return, but I'm not sure the best way to inject it into the CUT.  If my mock method creates the mock, it throws an error because mock() is not defined in the CUT, only in the unit test of course.  The service method looks something like:

<cffunction name="settle">

  <cfset result = getStripeBalance() />
  <cfloop array="#result.getParsedResult()#" index="local.balance">
      // do stuff
  </cfloop>

</cffunction>

I need to change what getParsedResult() returns when testing settle() (because it calls a remote third party service, Stripe).  The strategy I'm using in my test which seems crappy is:

<cffunction name="mock_return_balance">
    <cfreturn this.mock_balance />
</cffunction>

<cffunction name="testSettleStripe" output="false" access="public" returntype="any">

    <cfset variables.service.mock_balance = mock().getParsedResult().returns({"available": [{"amount": 5025, "currency": "usd"}, {"amount": 4750, "currency": "cad"}]}) />
    <cfset injectMethod(variables.service, this, "mock_return_balance", "getStripeBalance") />

    // do testing that calls a method settle() which internally calls getStripeBalance() which returns the public mocked object this.mock_balance.
    <cfset assertTrue(variables.service.settle() EQ true) />

</cffunction>


I don't like that I have to define a static variable name in my mock and then re-use it in my test method. Is there a better way?


Brian

Marc Esher

unread,
Aug 7, 2015, 3:15:23 PM8/7/15
to mxu...@googlegroups.com
My initial reaction here is to not have settle() call getBalance(),
but to instead pass in the Balance object as an argument to settle.

By using dependency injection, you get back full control of your test.
> --
> You received this message because you are subscribed to the Google Groups
> "mxunit" group.
> To unsubscribe from this group and stop receiving emails from it, send an
> email to mxunit+un...@googlegroups.com.
> To post to this group, send email to mxu...@googlegroups.com.
> Visit this group at http://groups.google.com/group/mxunit.
> For more options, visit https://groups.google.com/d/optout.

Brian G

unread,
Aug 14, 2015, 1:09:30 AM8/14/15
to mxunit


On Friday, August 7, 2015 at 12:15:23 PM UTC-7, Marc Esher wrote:
My initial reaction here is to not have settle() call getBalance(),
but to instead pass in the Balance object as an argument to settle.

By using dependency injection, you get back full control of your test.


Hrmmm... thanks for that Marc.  I don't want my controllers to be fetching the balance and passing it in because we deal with multiple payment gateways and I want to abstract that from the app into a simple nightly batch process, but I could refactor the methods inside of settle() to achieve the same thing.  Maybe:



<cffunction name="settle">

  <cfset result = getStripeBalance() />
  <cfset settleStripe(result) />

</cffunction>

And then I can test my settleStripe separately?  Thanks,


Brian

Marc Esher

unread,
Aug 18, 2015, 6:35:02 PM8/18/15
to mxu...@googlegroups.com
Brian,

That makes sense to me. Bottom line is that no matter where it happens
(service layer, controllers, whatever), you can't get full control
over your tests if the settle() method doesn't enable its inputs to be
changed at test time.

Marc
Reply all
Reply to author
Forward
0 new messages