SitePoint Sponsor

User Tag List

Results 1 to 11 of 11
  1. #1
    SitePoint Evangelist
    Join Date
    Jun 2003
    Location
    Melbourne, Australia
    Posts
    440
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)

    PHPUnit is very frustrating

    Well, only because I'm accustomed to Simpletest. But a lot of people are using it and I thought it would be prudent to try it.

    Here's the problem: I wish to mock a PDO object to see whether or not it's receiving the right query in the PDO:: prepare() method. Since, in PHPUnit, defining a Mock is quite verbose, I decided to give my TestCase class a property with a MockPDO object. In any case, I expect it to behave the same way for each of my tests.

    So I wrote an init() method which is called by the TestCase's constructor...
    PHP Code:
    class MyTest extends PHPUnit_Framework_TestCase {
      protected 
    $pdo;

      function 
    __construct() {
        
    parent::__construct();
        
    $this->init();
      }

      function 
    init() {
        
    $this->pdo $this->getMock(
          
    'PDO',  //class to mock
          
    array('__construct''prepare'), //methods to mock
          
    array('mysql:host=localhost;dbname=test''username''password'//constructor params
          
    'TestPDO',  //name of test class (optional)
          
    false  //if true, call original constructor
        
    );
      }

      function 
    testFirst() {
        
    //whatever
      
    }

      function 
    testSecond() {
        
    //whatever
      
    }

    When I run the test I get an error saying
    Code:
    Uncaught exception 'RuntimeException' with message 'Class "TestPDO" already exists.
    The reason this happens is because PHPUnit's test running apparatus adds each test method as a separate test case. Therefore, the TestCase is instantiated more than once if it has more than one test method.

    I could wrap the call to PHPUnit_Framework_TestCase::getMock() in something like
    PHP Code:
    if(!class_exists('TestPDO')) {
      
    $this->pdo $this->getMock/* args like those in the previous code sample */ );

    This works for the first test, but for the second the class already exists so the $pdo property is not instantiated. I suppose I could call it manually...
    PHP Code:
    if(!isset($this->pdo)) {
      
    $this->pdo = new TestPDO();

    The weird thing is that this problem doesn't arise if I call PHPUnit_Framework_TestCase::getMock() inside each test method, regardless of whether it's assigned to a local variable or a class property. Surely the class declaration is global in scope!? (Or maybe it's not ... this is doing my head in!)

    Anyway, I can think of at least one way of getting around this (e.g. calling the init() method from each test method. Of course, I'd need to return the mocked object -- well several since I wish to mock several classes and make instances of them available to all test methods; I gave PDO as just one example.

    This makes PHPUnit exceptionally difficult to use. I don't think I'm doing anything that's unreasonable. It's enough to drive one back to Simpletest! Or am I missing something obvious about mock objects in PHPUnit?

    Thanks in advance for any insights.

    P.S. Why is it that every time (and I mean every time) I want to enter a long post and do several previews to make sure it makes sense, Sitepoint becomes uncontactable (for 10 or more minutes) when I finally go to submit it?
    Last edited by auricle; Nov 3, 2007 at 05:41. Reason: Two adjacent characters were interpreted as an emoticon
    Zealotry is contingent upon 100 posts and addiction 200?

  2. #2
    simple tester McGruff's Avatar
    Join Date
    Sep 2003
    Location
    Glasgow
    Posts
    1,690
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    I'm afraid I don't know anything about PhpUnit. If there were more hours in the day I'd try it out but I never have enough time for everything I need to do.

  3. #3
    SitePoint Wizard silver trophy kyberfabrikken's Avatar
    Join Date
    Jun 2004
    Location
    Copenhagen, Denmark
    Posts
    6,157
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by auricle View Post
    Surely the class declaration is global in scope!? (Or maybe it's not ... this is doing my head in!)
    Yeah, classes always are (in PHP).


    I think this is your problem:
    Quote Originally Posted by auricle View Post
    PHP Code:
          'TestPDO',  //name of test class (optional) 
    Looking at the source for Framework/TestCase.php and Framework/MockObject/Mock.php you can see, that the method getMock() generates a new class. The classname is generated, to prevent nameclash, but if you explicitly specify a classname, this is used instead. I'm not sure, what the usecase for that is, but try without.

  4. #4
    SitePoint Wizard dreamscape's Avatar
    Join Date
    Aug 2005
    Posts
    1,080
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    use setUp() to setup each test.
    <.smarter.web.development.>
    PHP Stuff: Plexus | Chocolate (BDD Framework... coming soon)
    Graphite

  5. #5
    SitePoint Evangelist
    Join Date
    Jun 2003
    Location
    Melbourne, Australia
    Posts
    440
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by kyberfabrikken View Post
    Yeah, classes always are (in PHP).
    I spent a lot of time last night trying so many things that I begin to wonder whether everything I claimed about PHPUnit's behaviour was correct. In particular, I think I might be wrong about the re-use of a class name in the getMock() method called in a test method. Since classes are global and since each test method is called from a different instance of the TestCase class the "Class already exists" problem would occur.
    Quote Originally Posted by kyberfabrikken View Post
    Looking at the source for Framework/TestCase.php and Framework/MockObject/Mock.php you can see, that the method getMock() generates a new class. The classname is generated to prevent nameclash, but if you explicitly specify a classname, this is used instead. I'm not sure, what the usecase for that is, but try without.
    I wanted to name the class because other objects need to know the name of the class without having access to an instance of it -- therefore they can't just call the get_class() function. Thinking about it some more, I realise that if I mock those objects (as I actually had done), I can just pass the name to the mocks.

    In a way, it's rather annoying because nowhere in the (otherwise extensive) PHPUnit documentation does it say that you cannot re-use custom class names in successive tests or the reason why this is so: because each test is run in isolation from the others (from a separate instance of the TestCase). I can't think of a single reason why Sebastian Bergmann has chosen to design it this way... which is not to say that there's no good reason, as when it comes to coding he's a lot smarter than I am (as are most of you readers, I suspect). It also seems a bit paradoxical when the documentation espouses the benefits of creating test fixtures that can be used by more than one test method. OK, a mock is perhaps not a fixture but it may encapsulate some behaviour that one wishes to test that should be the same all the time. In reality, test fixtures are never re-used since only one test method is ever run per single instance of the test case.

    Dreamscape suggested using the setUp() method to do this. I tried that and had the same problem for precisely the same reasons. It doesn't matter where the mock class is defined; if it has the same name, the machinery that creates the mock will throw an error if the name is the same...precisely because the next test method will be run from a different instance of the TestCase.
    Zealotry is contingent upon 100 posts and addiction 200?

  6. #6
    SitePoint Wizard silver trophy kyberfabrikken's Avatar
    Join Date
    Jun 2004
    Location
    Copenhagen, Denmark
    Posts
    6,157
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by auricle View Post
    I wanted to name the class because other objects need to know the name of the class without having access to an instance of it -- therefore they can't just call the get_class() function.
    That sounds a bit peculiar to me. Why do they need to know that?

  7. #7
    SitePoint Member
    Join Date
    Jul 2006
    Location
    Prague, Czech republic
    Posts
    16
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    So what about this:
    PHP Code:
    public function setUp() {
        
    $this->pdo $this->getMock(...);
        
    $this->pdoClass get_class($this->pdo);


  8. #8
    SitePoint Evangelist
    Join Date
    Jun 2003
    Location
    Melbourne, Australia
    Posts
    440
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by kyberfabrikken View Post
    That sounds a bit peculiar to me. Why do they need to know that?
    I didn't really want to get into that because the thread is more about PHPUnit, but since you asked...!

    I'm looking again at some work I did -- mainly for fun -- back in March and April. I alluded to some testing issues with Simpletest in this thread.

    I wanted to write a simple ActiveRecord implementation which would do relatively simple queries, with joins if necessary. As a concrete example, I chose a blogging application (doesn't everyone!?) and the relationships between the different objects in it...
    Blog post : author = many-to-one
    Blog post : comment = one-to-many
    Blog post: category = many-to-many

    I decided to do something similar to what the ez guys did with ezComponents: have a "persistent session" object to do the actual DB interaction, and load the values into objects.

    Those objects are a bit more than that, though: they also express their relations to other objects. The session object "interrogates" an instance of a value object to discover the nature of those relations and build the query. If the relation is one-to-one or many-to-one, a single query can be issued; if one- or many-to-many, separate queries would be issued.

    The relation is expressed through an Association object (well, that's the name of my class). As its properties, it holds the foreign key used in the relation and the classname of the associated object to create (or fetch data for).

    The library allows users to nominate which fields they wish to fetch. this is done by passing a "prototype" object in...
    PHP Code:
    $post = new Post();
    $post->with('Post', array('id''author_id''content')); // nominate Post fields
    $post->with('Author', array('id''name')); //nominate Author fields

    $session = new Session($pdo);  //pass in a PDO object
    $session->findAll($post); 
    Because the Post prototype has the fields set, the session object should assemble and issue a query something like
    Code:
    SELECT posts.id, posts.author_id, posts.post, authors.id, authors.name FROM posts, authors WHERE posts.author_id = authors.id
    The assembly of the query depends on the Association object which expresses the relation. When I referred to needing to know the name of the mock object's class I meant that the Post, Author, and Association objects would be mocked and that the mock Association expressing the relationship of Author to Post needed to know the name of the mocked Post class for its $classname property. This is why I initially decided to explicitly name the mocked class. In a real Association object, the $classname property would just be set, but in the mock I have to explicitly set it based on the classname of the mocked Post (which changes every time it's mocked).

    Anyway, that problem is solved. There's another which I'll ask about in another post.
    Zealotry is contingent upon 100 posts and addiction 200?

  9. #9
    SitePoint Evangelist
    Join Date
    Jun 2003
    Location
    Melbourne, Australia
    Posts
    440
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Thank you all for your patience with this.

    Now PHPUnit is frustrating me because its sequencing of responses by mocked method calls doesn't seem to work.

    Following on from the last post: I want my Session object to go through a prototype's associations in order to build queries... something like
    PHP Code:
    function find($prototype) { //Session class method
      //some other code, then...
      
    foreach($prototype->isWith() as $classname => $fields) {
        if(
    $association $prototype->getAssociation($classname)) {
          
    //there is an association with $classname: do whatever to build bits of the query
        
    }
      }

    I'm testing the session class so I'm mocking the prototype object. Therefore, the mock must behave as the prototype would when its getAssociation() method is called. In this example, I'm fetching only Posts and not Authors, so there is only one set of fields returned by $prototype->isWith()... those of the Post class. However, I want to set up the Mock so that it returns different sets of fields depending on the $classname passed to the getAssociation() method.

    As I recall, in Simpletest one could do something like
    PHP Code:
    $mock->setReturnValue('getPetNoise''woof', array('dog')) 
    That is, whenever the value 'dog' is passed as an argument to $mock->getPetNoise(), return 'woof'. PHPUnit doesn't quite work that way. As far as I can tell, the only way to mock different responses from a method is to do it sequentially...
    PHP Code:
    $mockPost->expects($this->at(0)) //$this->at() is the sequential bit
        
    ->method('getAssociation')
        ->
    with($this->equalTo('Post'))
        ->
    will($this->returnValue(null));  //Post has no association with itself
    $mockPost->expects($this->at(1)) //the second time getAssociation is called
        
    ->method('getAssociation')
        ->
    with($this->equalTo('Author'))
        ->
    will($this->returnValue($authorToPostAssociation)); 
    My problem is that if I make the single call $mock->getAssociation('Post') I get an error saying that it expected 'Author' as an argument, even though that was the first time (index = 0). In other words, the PHPUnit mock appears to be ignoring the sequence altogether and be just expecting the last value I defined for it.

    (OK, not Post and Author but the mocked classnames... but you get the idea.)

    Just to see what would happen I tried a different expectation which I placed before the other two:
    PHP Code:
    $mock->expects($this->exactly(2))
        ->
    method('getAssociation'); // expect two calls to getAssociation 
    That expectation should fail since getAssociation() is called only once. In fact, it was ignored until I commented out the other two. This suggests that in a PHPUnit mock object one can set only one expectation for a method at a time. If this is true, then that's a reason right there for going back to Simpletest and forgetting about PHPUnit altogether.

    I hope I'm wrong. There's a good chance of that because examples like this are just not given in the documentation. If anyone here knows how to set up multiple expectations properly so that they all work, I'd be very grateful to receive a reply.
    Last edited by auricle; Nov 5, 2007 at 05:34. Reason: bad grammar and incorrect comment in code example
    Zealotry is contingent upon 100 posts and addiction 200?

  10. #10
    SitePoint Evangelist
    Join Date
    Jun 2003
    Location
    Melbourne, Australia
    Posts
    440
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Making some progress now. I completely missed a part of the Stubs API. So I can do this:
    PHP Code:
    $mockPost->expects($this->any())
        ->
    method('getAssociation')
        ->
    will($this->onConsecutiveCalls(null$authorToPostAssociation)); 
    OnConsecutiveCalls() provides for different return values. However, if one sets a return of a null value, PHPUnit throws an error about the method not existing (I don't understand that at all -- what's wrong with returning a null value?).

    I thought about it a bit though and realised I didn't have to define a return value for the first time the method is called. So I ended up with:
    PHP Code:
    $this->post->expects($this->at(1))
      ->
    method('getAssociation')
      ->
    will($this->returnValue($this->authorToPostAssociation)); 
    I haven't used ->with() this time because it throws the error I described in the previous post. I still think that's screwed up because it means you can't define a response to a call you're not making. In this case, I've defined what should happen in response to the second call of the method but I've made only the first call.

    You may remember that I want to set up the behaviour of the mock in the setUp() method, but this won't work. I'd have to move all that stuff into each method and make it explicit for the specific test: in the first test I'd have to leave the response undefined and in the second I'd have to define it only for the second call. It sure would be nice to be able to do it all in the one place and not repeat myself.

    Furthermore, I'm not actually interested in the sequence of calls to the getAssociation method; I simply want to define that when that method receives one parameter it does nothing and when it receives the other it returns an object. Simpletest allows that so easily... and it gives you sequenced responses if you want to use them.

    ---

    I may be wrong but it appears that $this->at() used in the second code sample requires indices starting at 1, not 0.

    Oh Mr Bergmann, you've got a lot of explaining to do.
    Last edited by auricle; Nov 6, 2007 at 05:56. Reason: New information
    Zealotry is contingent upon 100 posts and addiction 200?

  11. #11
    SitePoint Enthusiast
    Join Date
    Mar 2005
    Posts
    94
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by auricle View Post
    You may remember that I want to set up the behaviour of the mock in the setUp() method, but this won't work. I'd have to move all that stuff into each method and make it explicit for the specific test: in the first test I'd have to leave the response undefined and in the second I'd have to define it only for the second call. It sure would be nice to be able to do it all in the one place and not repeat myself.

    Furthermore, I'm not actually interested in the sequence of calls to the getAssociation method; I simply want to define that when that method receives one parameter it does nothing and when it receives the other it returns an object. Simpletest allows that so easily... and it gives you sequenced responses if you want to use them.
    i suggest to test each case separately because the object don't seems
    to have a internal state.

    also i think its very good to have the test fixture in the test method, because you can see everything that's related to this test. if you have too big fixtures there is something wrong in your tests or your code, so you don't really need to "be DRY" in your tests.


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
  •