skef has asked for the wisdom of the Perl Monks concerning the following question:

Hello! Since I have what is probably and X/Y question, I'm going to describe both.

Context: I am developing unit tests for a Catalyst application. The Model is strictly relational, and consists of a straightforward Moose object many (100+) subroutines spread out over 15 or so Roles, but with associated SQL code somewhat specific to a database that I can't just make a little clone of for testing. I already have a system for testing this module that I am happy with. With that done, I would prefer to test the rest of the server code path with a fake DB object.

This would be roughly a "Mock" object, but I sometimes need it to be sensitive to input parameters, so call it "Mock+". It's technically easy to build such a thing, but given the large number of methods and the variety and sometimes complexity of the return values, I think it would take quite a bit of time.

I would prefer a record/replay solution like the following:

Record: One class takes another as a constructor argument, and then instantiates that object and "impersonates" its interface. When methods are called, it records the method and arguments as a "key", and return value as a "value". (Repeated keys can overwrite or discard a different return value -- it doesn't matter.) This goes to some file format either as the methods are called or at all at once when a method is called or the object is destructed.

Replay: Another class impersonates the same interface but doesn't instantiate the object. Instead, it turns the method/argument combinations into keys and returns the corresponding value if present. Otherwise it calls a default function (perhaps passing some version of the key, but I don't need that).

Return values need not include any function pointers, blessed objects and such. Parameter values of those types can just be treated as a string encoding the function or class name. Compromises such as turning the "key" into a unique string and storing an MD5 are fine.

So: 1) Does such a system already exist? And if not, 2) what projects/CPAN packages etc. would provide a useful reference or 3) what are the pieces with which such a (not necessarily CPAN-ready) system could be whipped up quickly?

Replies are listed 'Best First'.
Re: Mock+ object with recording?
by choroba (Cardinal) on Nov 30, 2017 at 10:34 UTC
    I'm not aware of any such "recording" package.

    For mocking a database, we use DBD::Mock. Setting up the object is a bit exhausting, but in the end, it works greatly.

    See also Test::Spec::Mocks for mock objects that can verify they arguments and return different values based on them.

    Also note that you should mock the DB only for the layers that talk to it directly. For all the layers above that, you should mock the directly underlying layer and forget about what's below.

    ($q=q:Sq=~/;[c](.)(.)/;chr(-||-|5+lengthSq)`"S|oS2"`map{chr |+ord }map{substrSq`S_+|`|}3E|-|`7**2-3:)=~y+S|`+$1,++print+eval$q,q,a,

      For your last reason, I was already planning to avoid any DBI stuff and just Mock my Model object.

Re: Mock+ object with recording?
by skef (Novice) on Nov 30, 2017 at 11:07 UTC

    Ideas: (edited to include AUTOLOAD)

    A reasonable strategy for the key might be to shift $self from @_ and convert the array to canonical JSON (adding object serialization TO_JSON calls as needed). The value could be handled similarly.

    AUTOLOAD seems like the right hook for simplifying the interposition. Basically make both the record and replay objects have only AUTOLOAD methods, which then dispatch to the sub-object in the record case and only convert the key and look for the stored value.

    "Impersonation" might be as simple as overloading isa()?

    .

    Maybe I have enough pieces to play around with this, but any advice is still greatly appreciated.

      > should be possible with MOP

      Or maybe just AUTOLOAD?

      ($q=q:Sq=~/;[c](.)(.)/;chr(-||-|5+lengthSq)`"S|oS2"`map{chr |+ord }map{substrSq`S_+|`|}3E|-|`7**2-3:)=~y+S|`+$1,++print+eval$q,q,a,
Re: Mock+ object with recording?
by Anonymous Monk on Nov 30, 2017 at 16:28 UTC
    Because of the complexity of setting up a true, database-free mock, and the concern that this mock object actually performs as expected in all cases, what I would instead do is to set up a small, separate, test database which has the same structure as the original but only a smattering of (fake, or sanitized) data. Completely different passwords, etc. Then, point your test-scripts to that database, making damn sure(!) that they are in an environment which cannot access production by any means whatever. Consider taking a dump of that database's "initial, pristine" contents so that you can quickly restore it during the test cycle.