Neat SOAP::Lite mock interface hack

Ovid on 2004-10-15T23:03:04

In order to isolate some behavior in my SOAP::Lite tests, I wanted to ensure two things. First, I wanted to know that the SOAP request XML matched my expectations. Second, I wanted to ensure that the SOAP response XML was correctly parsed. In neither case did I want to actually make the round-trip to our customer's SOAP server.

package MOCK::SOAP::INTERFACE;
use Sub::Override;

use overload
    '""' => \&request;

my $REQUEST; # this is the SOAP *request* XML

sub _mock_soap_interface
{
    my $xml   = shift; # this is the SOAP *response* XML
    my $token = Sub::Override->new(
        'SOAP::Lite::call',
        sub { 
            my $self = shift;
            $REQUEST = $self->serializer->envelope(method => shift(@_), @_);
            return $xml;
        }
    );
    bless $token => __PACKAGE__;
    return $token;
}

sub request { return $REQUEST }

1;

Because overridden subs from Sub::Override are lexically scoped, the caller has to hold onto the returned token in order to keep it in scope. By blessing that token into my mocked class, I can then overload stringification and allow it to represent the SOAP request XML that &SOAP::Serializer::envelope returns. Then I write my test something like this:

sub test_my_soap : Test(2) {
    my $xml = <<'END_XML';


END_XML
                                                                                                                               
    my %expected_circuits = (
        # some data
    );
    my $expected_xml = # expected *request* XML
    my $response     = _mock_soap_interface($xml);
    my $soap         = MY::SOAP::Package->new;

    my $circuits = $soap->get_circuits;

    is_deeply(
        $circuits,
        \%expected_circuits
    );
    # quote response to break the overloading
    is_xml("$response", $expected_xml);
}

Yeah, there's some hackish stuff there, but one thing I really like is that now, when you're reading the tests, the programmer is much more likely to remember to hold on to the returned token ($response). Still, it looks weird having a scalar who's value is going to change for no apparent reason. I can't say that I totally like this approach, but I was pretty durned pleased with myself.

I think that I might just have to offer some sort of overloading support for Sub::Override so that the returned token actually does something useful.