SitePoint Sponsor

User Tag List

Results 1 to 23 of 23
  1. #1
    SitePoint Zealot
    Join Date
    May 2001
    Posts
    193
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)

    SimpleTest MockObject question?

    Sorry if this isn't the right place for this question.

    I'm trying to use a mock object for database results and im not sure how to go about this. My problem is that $subscriberBase->findAllSubscribers() returns an array of Subscriber objects. the first call to execute() is "SELECT * FROM subscribers", then it takes the resulting array and makes Subscriber objects which contains another call to execute() to get the Subscribers "subscriptions" . The problem im having is i get an error saying my call to getRow() is on a non-object when trying to get the subscriptions.

    If i remove the nested call to execute(), i get that lovely green bar

    So basically, how do i handle nested calls to this mock object?

    PHP Code:
        function testFindAllSubscribers()
        {
            
    $dao =& new MockDao($this);
            
    $results =& new MockDataAccessResult($this);
            
    $results->setReturnValue('getRow'false);
            
    $results->setReturnValueAt(0'getRow', array("name"=>"Mike"));
            
    $results->setReturnValueAt(1'getRow', array("name"=>"Joe"));
            
    $dao->setReturnReference('execute'$results, array('SELECT * FROM subscribers'));
            

            
    $subscriberBase = new SubscriberBase($dao);
            
    $subscribers $subscriberBase->findAllSubscribers();
            
            
    $this->assertEqual($subscribers[0]->get('name'), 'Mike');
               
    $this->assertEqual($subscribers[1]->get('name'), 'Joe');
        } 
    Again, sorry if this is not the appropriate forum for this.
    Thanks

  2. #2
    eschew sesquipedalians silver trophy sweatje's Avatar
    Join Date
    Jun 2003
    Location
    Iowa, USA
    Posts
    3,749
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Looks basically right to me. Is this php4? Are all objects being passed and returned by reference?
    Jason Sweat ZCE - jsweat_php@yahoo.com
    Book: PHP Patterns
    Good Stuff: SimpleTest PHPUnit FireFox ADOdb YUI
    Detestable (adjective): software that isn't testable.

  3. #3
    SitePoint Zealot
    Join Date
    May 2001
    Posts
    193
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Im testing this with php5, so by default they are getting passed by reference, right?

  4. #4
    Non-Member
    Join Date
    Jan 2004
    Location
    Planet Earth
    Posts
    1,764
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Yep, by reference

  5. #5
    SitePoint Zealot
    Join Date
    May 2001
    Posts
    193
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    here is my test

    PHP Code:
        function testFindAllSubscribers()
        {
            
    $dao =& new MockDao($this);
            
    $results =& new MockDataAccessResult($this);
            
    $results->setReturnValue('getRow'false);
            
    $results->setReturnValueAt(0'getRow', array("id"=>1"name"=>"Mike"));
            
    $results->setReturnValueAt(1'getRow', array("id"=>2"name"=>"Joe"));
            
    $dao->setReturnReference('execute'$results, array('SELECT * FROM subscribers'));
            

            
    $subscriberBase = new SubscriberBase($dao);
            
    $subscribers $subscriberBase->findAllSubscribers();
            
            
    $this->assertEqual($subscribers[0]->get('name'), 'Mike');
               
    $this->assertEqual($subscribers[1]->get('name'), 'Joe');
        } 

    here is what SubscriberBase looks like:

    PHP Code:
    class SubscriberBase{

        var 
    $dao;

        function 
    SubscriberBase($dao)
        {
            
    $this->dao $dao;
        }

        function 
    findAllSubscribers()
        {
            
    $subscribers $this->dao->execute"SELECT * FROM subscribers" );
            
            while(
    $subscriber $subscribers->getRow())
            {
                
    $objArray[] = $this->_getObject($subscriber);
            }
            
            return 
    $objArray;
        }

        function 
    _getObject($row)
        {

            
    $subscriber = new Subscriber();

            
    $subscriber->setFirstName($row['name']);

            
    $subscriptions $this->getSubscriptions($row['id']);
            
            while(
    $subscription $subscriptions->getRow())
            {
                
    $subscriber->addSubscription($subscription['id']);
            }

            return 
    $subscriber;
        }


        function 
    getSubscriptions($id)
        {
            
    $subscriptions $this->dao->execute"SELECT id, name FROM newsletters AS N, subscriptions AS S WHERE S.subscriber_id=$id AND N.id=S.newsletter_id" );
            
            while(
    $subscription $subscriptions->getRow())
            {
                
    $subscriber->addSubscription($subscription['id']);
            }
        }

    and here is the error
    Code:
    Fatal error: Call to a member function getRow() on a non-object in class.SubscriberBase.php on line 98
    Which is happeining in __getObject() when calling getSubscriptions(), so im wondering if i need to set another setReturnReference() for this call?
    Last edited by MikeShank; Aug 28, 2004 at 07:14.

  6. #6
    eschew sesquipedalians silver trophy sweatje's Avatar
    Join Date
    Jun 2003
    Location
    Iowa, USA
    Posts
    3,749
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by Widow Maker
    Yep, by reference
    by handle actually, though in practice they end up nearly the same

  7. #7
    eschew sesquipedalians silver trophy sweatje's Avatar
    Join Date
    Jun 2003
    Location
    Iowa, USA
    Posts
    3,749
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    MikeShank, can we see some more of your actual code to see if we can spot any possible issues? And, what version of SimpleTest are you using? We just located some php5 issues that were corrected for RC1

  8. #8
    Non-Member
    Join Date
    Jan 2004
    Location
    Planet Earth
    Posts
    1,764
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    though in practice they end up nearly the same
    You just had to get that in aye ?

  9. #9
    SitePoint Zealot
    Join Date
    May 2001
    Posts
    193
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    im using version 1.0beta6
    I posted the code i am using above
    Thanks for your input

  10. #10
    eschew sesquipedalians silver trophy sweatje's Avatar
    Join Date
    Jun 2003
    Location
    Iowa, USA
    Posts
    3,749
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Since I know there have been php5 specific fixes in SimpleTest, please upgrade to 1.0RC1 and try that.

    Also, you have not shown enough of the SubscriberBase class, specifically the $this->dao assignment.

  11. #11
    SitePoint Zealot
    Join Date
    May 2001
    Posts
    193
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    sorry about that, i edited my code above to show the assignment

  12. #12
    ********* Victim lastcraft's Avatar
    Join Date
    Apr 2003
    Location
    London
    Posts
    2,423
    Mentioned
    2 Post(s)
    Tagged
    0 Thread(s)
    Hi...

    Quote Originally Posted by MikeShank
    Sorry if this isn't the right place for this question.
    SimpleTest does have it's own mail list on Sourceforge, but hey, we get the free publicity .

    Quote Originally Posted by MikeShank
    PHP Code:
        function testFindAllSubscribers()
        {
            
    $dao =& new MockDao($this);
            
    $results =& new MockDataAccessResult($this);
            
    $results->setReturnValue('getRow'false);
            
    $results->setReturnValueAt(0'getRow', array("name"=>"Mike"));
            
    $results->setReturnValueAt(1'getRow', array("name"=>"Joe"));
            
    $dao->setReturnReference('execute'$results, array('SELECT * FROM subscribers'));
                    ...
        } 
    What happens when you "play back" the mock...
    PHP Code:
    function testFindAllSubscribers() {
        ...
        
    $rows $dao->execute('SELECT * FROM subscribers');
        
    $this->dump($rows->getRow());
        
    $this->dump($rows->getRow());

    Does it behave the way you actually set it too?

    Also you have a mixture of PHP4 code and PHP5 with regard to references. When getting the last SimpleTest release out it broke between PHP5 beta4 and PHP5.0.1 on precisely this issue. I suspect that PHP5 has some issues with hybrid code, where you pass an object handle by reference for example. Just guessing, but you could try dropping the ampersands from the "new" statements.

    yours, Marcus
    Marcus Baker
    Testing: SimpleTest, Cgreen, Fakemail
    Other: Phemto dependency injector
    Books: PHP in Action, 97 things

  13. #13
    eschew sesquipedalians silver trophy sweatje's Avatar
    Join Date
    Jun 2003
    Location
    Iowa, USA
    Posts
    3,749
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by lastcraft
    Also you have a mixture of PHP4 code and PHP5 with regard to references. When getting the last SimpleTest release out it broke between PHP5 beta4 and PHP5.0.1 on precisely this issue. I suspect that PHP5 has some issues with hybrid code, where you pass an object handle by reference for example. Just guessing, but you could try dropping the ampersands from the "new" statements.
    From what I understand, passing objects by reference should not matter either way in php5. The distinction is only in what happens to the variable rather than the object, i.e.:

    PHP Code:
    function doSomething($one, &$two) {
      
    $one->'new';
      
    $one false;
      
    $two->'new';
      
    $two false;
    }
    class 
    Bar { public $x 'default'; }
    $foo = new Bar;
    $blah = new Bar;
    doSomething($foo$blah);
    var_dump($foo$blah); 
    results in
    Code:
    object(Bar)#1 (1) {
      ["x"]=>
      string(3) "new"
    }
    bool(false)

  14. #14
    SitePoint Zealot
    Join Date
    May 2001
    Posts
    193
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    if i comment out
    PHP Code:
    subscriptions $this->getSubscriptions($row['id']); 

    while(
    $subscription $subscriptions->getRow()) 
    {
    $subscriber->addSubscription($subscription['id']); 

    in _getObject(), and do a $this->dump($subscriberBase) it gives me the array of Subscribers with the correct values.
    Also i did remove the & and it does not seem to have an effect, it still chokes on the second call to execute() in getSubscriptions()

  15. #15
    eschew sesquipedalians silver trophy sweatje's Avatar
    Join Date
    Jun 2003
    Location
    Iowa, USA
    Posts
    3,749
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Look at the dao->execute above where your test case is failing:

    PHP Code:
    $subscriptions $this->dao->execute
        
    "SELECT id, name FROM newsletters AS N, subscriptions AS S WHERE S.subscriber_id=$id AND N.id=S.newsletter_id" ); 
    the signature that you mocked for the dao was 'SELECT * FROM subscribers' not the select statement you have there.

  16. #16
    SitePoint Zealot
    Join Date
    May 2001
    Posts
    193
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    yes, I know the execute calls are different. That was my question, how do i handle nested call to execute().
    Basicallly how would you test what i am testing here. you want execute( "SELECT * FROM subscribers" ) to return an array of subscribers and you want execute(
    ****"SELECT id, name FROM newsletters AS N, subscriptions AS S WHERE S.subscriber_id=$id AND N.id=S.newsletter_id" ) to return an array of newsletters.

  17. #17
    eschew sesquipedalians silver trophy sweatje's Avatar
    Join Date
    Jun 2003
    Location
    Iowa, USA
    Posts
    3,749
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    something along the lines of :
    PHP Code:
            $dao = new MockDao($this);
            
    $results = new MockDataAccessResult($this);
            
    $results->setReturnValue('getRow'false);
            
    $results->setReturnValueAt(0'getRow', array("name"=>"Mike"'id'=>1));
            
    $results->setReturnValueAt(1'getRow', array("name"=>"Joe"'id'=>5));
            
    $dao->setReturnReference('execute'$results, array('SELECT * FROM subscribers'));
            
    $nullset =  new MockDataAccessResult($this);
            
    $nullset->setReturnValue('getRow'false);
            
    $resultsSub1 = new MockDataAccessResult($this);
            
    $resultsSub1->setReturnReference('getRow'$nullset);
            
    $dao->setReturnReference('execute'$resultsSub1, array(
                new 
    WantedPatternExpectation('/SELECT.*newsletters.*_id=1/i')));
            
    $resultsSub5 = new MockDataAccessResult($this);
            
    $resultsSub5->setReturnReference('getRow'$nullset);
            
    $dao->setReturnReference('execute'$resultsSub5, array(
                new 
    WantedPatternExpectation('/SELECT.*newsletters.*_id=5/i'))); 

  18. #18
    ********* Victim lastcraft's Avatar
    Join Date
    Apr 2003
    Location
    London
    Posts
    2,423
    Mentioned
    2 Post(s)
    Tagged
    0 Thread(s)
    Hi...

    Quote Originally Posted by sweatje
    something along the lines of :
    Very clever. I thought we were tracing another bug report there just before the final release .

    Now a confession . In using the database as an example of mock objects I think I made a big mistake. It can be done, but it's really not appropriate for testing SQL statements. After all, you are really testing not just the mechanics of your iterator, but the connection between the data and the SQL statement. I've been down this road and here is the problem I eventually ran into.

    It all goes great at first, your tests run fast and (given the mental agility of someone like Jason) quick and easy to write. The problem is that the tests are brittle. If you change the table name, say, you will have to go back and edit every single test. When mocks span a translation layer, and you are acting as a human compiler trying to translate things by hand, I'd get suspicious. By all means make a test of the iterator mechanics this way (mocks are good at that), but for anything depending on query text I'd use a real database. You can place these tests in a separate test suite so the unite tests don't depend on installed components and run quicker. I now place the database mapping tests into integration tests.

    Which makes my old PHP|Architect article on the subject a little misleading . What can I say? You live and learn.

    yours, Marcus
    Marcus Baker
    Testing: SimpleTest, Cgreen, Fakemail
    Other: Phemto dependency injector
    Books: PHP in Action, 97 things

  19. #19
    SitePoint Zealot
    Join Date
    May 2001
    Posts
    193
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Thanks guys,
    It did seem to me that it is more work to mock the db, then it would be to just use an actual db. But then again I'm just beginning with testing an don't fully understand the consequences of doing that. Thanks for your help.

  20. #20
    SitePoint Zealot
    Join Date
    May 2001
    Posts
    193
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    after going thru my code, the issue was with my _getObject() method trying to iterate thru the returned results from getSubscriptions() when I didn't have anything being returned from that method. A stupid mistake on my part, sorry about that, but I did learn some info in using mocks. Thanks again.

  21. #21
    ********* Victim lastcraft's Avatar
    Join Date
    Apr 2003
    Location
    London
    Posts
    2,423
    Mentioned
    2 Post(s)
    Tagged
    0 Thread(s)
    Hi...

    Quote Originally Posted by MikeShank
    It did seem to me that it is more work to mock the db, then it would be to just use an actual db.
    Not exactly. For tests that don't depend on the select statements they should be fine. For example, that your subscription objects are created from the data correctly is much more easily done with mocks because you can control the rows coming back without worrying about the SQL. It's when you test across layers there are problems, in this case SQL to objects.

    You can run into similar problems with parsers. Suppose you have a lexer that splits the incoming stream into tokens and a parser that produces events from this. It is likely that you will make changes to the lexer and parser together. Changing the lexer means changing the token boundaries. If you have mocked token streams then this is going to be tedious to change in the all those tests of the parser. It is easier to refactor if they are tested together.

    The catch is that you need to break the problem down initially, especially if you are using a fine grain TDD cycle. You have to start with one object and test that. So far you have done fine in that you know the basic iterator mechanics are working and so doing the first few tests with mocks hasn't wasted. It's just that it is time to switch to end to end testing once you start getting such fiddly assertions on implementation details such as table names.

    You can still test everything with mocks and it will work well. Too well, to the point of being seductive. Refactoring code means refactoring the tests as well. It's just that it pays to keep an eye on both to avoid future hassle.

    yours, Marcus
    Marcus Baker
    Testing: SimpleTest, Cgreen, Fakemail
    Other: Phemto dependency injector
    Books: PHP in Action, 97 things

  22. #22
    SitePoint Zealot
    Join Date
    May 2001
    Posts
    193
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Alright, alright! I tried to take the easy way out.
    I now see your point about controlling the rows coming back without worrying about the sql. I remeber reading some posts about TDD and people saying once you do it you wont code any other way, I have to admit it is addictive. btw SimpleTest is a great tool, thanks.

  23. #23
    SitePoint Guru dagfinn's Avatar
    Join Date
    Jan 2004
    Location
    Oslo, Norway
    Posts
    894
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by sweatje
    From what I understand, passing objects by reference should not matter either way in php5. The distinction is only in what happens to the variable rather than the object, i.e.:
    You're exactly right, but that difference makes a lot of difference. PHP 4 "references" (as the manual points out, they aren't) act strangely and counter-intuitively in some situations, especially when you try to build complex object-oriented structures. So I would advise everyone to avoid the ampersand in PHP 5 unless they're sure they know what they're doing.

    If you have a working PHP 4 application that uses "references", you can obviously keep them, but I would phase them out gradually.
    Dagfinn Reiersøl
    PHP in Action / Blog / Twitter
    "Making the impossible possible, the possible easy,
    and the easy elegant"
    -- Moshe Feldenkrais


Bookmarks

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •