Sign In/My Account | View Cart  
advertisement


Listen Print

A Test::MockObject Illustrated Example
by chromatic | Pages: 1, 2, 3, 4, 5, 6

For the second pass, I will test what happens when I provide a node to which to bind the form object. In an unmocked Everything, this node comes as an argument to the function. In the tests, I must return them from the mocked getParamArray(), so that's where I will start. I also set the 'field' value in the mock object to a sentinel value I'll check for later. Since the value of $field will be 'field,' it works out nicely. (There's room to be much more creative on these names, especially if you're trying to sneak the name of a friend into your software.)


        $gpa{bn} = $mock;
        $mock->{field} = 'bound node';

Because getNode() has a series set on it and hasn't been called before, it will return 0. That means that isOfType() won't be called on the author object, and the default choice won't be modified from its undefined value. These tests are fairly easy:


        genObject( $mock );
        
        ($method, $args) = $mock->next_call();
        isnt( $method, 'isOfType',
        '... not checking bound node type if it is not found' );
        
        shift @$args;
        %args = @$args;
        is( $args{-default}, undef, '... and not modifying 
             default selection' );
            

As before, next_call() comes in handy. Since I already know that textarea() will be the first (and last, for this pass) method called, I can make sure that isOfType() was not called.

Two tests follow. One ensures that the code checks the node's type. The other makes sure that the default value becomes a blank string if the type is incorrect. To make this work, I had to modify the existing getNode() series to return two instances of $mock.


        $mock->{title} = 'bound title';
        $mock->set_series( 'isOfType', 0, 1 );
        
        genObject( $mock );
        
        ($method, $args) = $mock->next_call( 2 );
        is( $method, 'isOfType', '... if bound node 
            is found, should check type' );
        is( $args->[1], 'user', '... (the user type)' );
        
        ($method, $args) = $mock->next_call();
        shift @$args;
        %args = @$args;
        is( $args{-default}, '',
            '... setting default to blank string 
                 if it is not a user' );

The only new idea here is of passing an argument to next_call(). I know getNode() is the first mocked method, so I can safely skip it. These tests all pass. The final testable condition is where the bound node exists and is a 'user' type node. The set_series() call in the last test block makes isOfType() return true for this pass:


        genObject( $mock );
        ($method, $args) = $mock->next_call( 3 );
        shift @$args;
        %args = @$args;
        is( $args{-default}, 'bound title', '... but using 
             node title if it is' );

I now have 22 successful tests. My original test name plan had 14 tests, but that number generally grows as I see more logic branches. I could add more tests, making sure that default values are not overwritten, and that the essential (hardcoded) attributes of the textfield() are set, but I'm reasonably confident in the tests as they stand. If something breaks, then I'll add a test to catch the bug before I fix it, but what's left is simple enough; I doubt it will break. (Writing that is a good way to have to eat my words later.)

Testing Another Method (A Less Detailed Example)

One method remains for this form Object: cgiVerify(). When the Engine processes input from submitted Form Object forms, it must rebuild the objects. Then, it checks the input against allowed values for the widgets. This method does just that. Its code is slightly longer, and reads:


        my ($this, $query, $name, $USER) = @_;
        
        my $bindNode = $this->getBindNode($query, $name);
        my $author = $query->param($name);
        my $result = {};
        
        if($author)
        {
                my $AUTHOR = $DB->getNode($author, 'user');
                
                if($AUTHOR)
                {
                        # We have an author!!  Set the CGI param 
                        # so that the
                        # inherited cgiUpdate() will just do 
                        # what it needs to!
                        $query->param($name, $$AUTHOR{node_id});
                }
                else
                {
                        $$result{failed} = "User '$author' 
                             does not exist!";
                }
        }
        
        if($bindNode)
        {
                $$result{node} = $bindNode->getId();
                $$result{failed} = "You do not have permission"
                        unless($bindNode->hasAccess($USER, 'w'));
        }
        
        return $result;

Rather than describing the writing of the tests (and my steps and missteps therein), I'll just comment on the tests themselves.


        my $qmock = Test::MockObject->new();
        $mock->set_series( 'getBindNode', 0, 0, 0, 
             $mock, $mock );
        $qmock->set_series( 'param', 0, 'author', 'author' );

Because of the way this method handles things, it's clearer to create another mock object to pass in as $query. I'm also setting up the two main series used for the several passes through this method. While writing the tests, I kept adding new values to these series. This is what remained at the end. This approach makes more sense to me than setting each mock before each pass, but it's a matter of style, and either way will work.


        $result = cgiVerify( $mock, $qmock, 'boundname' );

        ($method, $args) = $mock->next_call();
        is( $method, 'getBindNode', 'cgiVerify() should get 
             bound node' );
        is( join(' ', @$args), "$mock $qmock boundname",
                '... with query object and query 
                     parameter name' );

Here's the reason I used separate mock objects: to tell them apart as arguments in the getBindNode() call.

Pages: 1, 2, 3, 4, 5, 6

Next Pagearrow