views:

446

answers:

3

I am attempting to test a web service interface class using PHPUnit. Basically, this class makes calls to a SoapClient object. I am attempting to test this class in PHPUnit using the "getMockFromWsdl" method described here:

http://www.phpunit.de/manual/current/en/test-doubles.html#test-doubles.stubbing-and-mocking-web-services

However, since I want to test multiple methods from this same class, every time I setup the object, I also have to setup the mock WSDL SoapClient object. This is causing a fatal error to be thrown:

Fatal error: Cannot redeclare class xxxx in C:\web\php5\PEAR\PHPUnit\Framework\TestCase.php(1227) : eval()'d code on line 15

How can I use the same mock object across multiple tests without having to regenerate it off the WSDL each time? That seems to be the problem.

--

Having been asked to post some code to look at, here's the setup method in the TestCase:

protected function setUp() {
    parent::setUp();

    $this->client = new Client();

    $this->SoapClient = $this->getMockFromWsdl(
        'service.wsdl'
    );

    $this->client->setClient($this->SoapClient);
}
A: 

For basic usage, something like this would work. PHPUnit is doing some magic behind the scenes. If you cache the mock object, it won't be redeclared. Simply create a new copy from this cached instance and you should be good to go.

<?php
protected function setUp() {
    parent::setUp();

    static $soapStub = null; // cache the mock object here (or anywhere else)
    if ($soapStub === null)
        $soapStub = $this->getMockFromWsdl('service.wsdl');

    $this->client = new Client;
    $this->client->setClient(clone $soapStub); // clone creates a new copy
}
?>

Alternatively, you can probably cache the class's name with get_class and then create a new instance, rather than a copy. I'm not sure how much "magic" PHPUnit is doing for initialization, but it's worth a shot.

<?php
protected function setUp() {
    parent::setUp();

    static $soapStubClass = null; // cache the mock object class' name
    if ($soapStubClass === null)
        $soapStubClass = get_class($this->getMockFromWsdl('service.wsdl'));

    $this->client = new Client;
    $this->client->setClient(new $soapStubClass);
}
?>
Pestilence
I posted some code up in the question for review. Placing in a class_exists doesn't seem like it'd solve the issue, as you still have to retrieve the mock object somehow and this class will always run that eval. Unless I were to modify PHPUnit itself?What did you have in mind in where to place the class_exists() method?
scraton
No. Stop. PHPUnit does *not* need modification. Seriously. Why are you using eval? The class that executes eval is the problem. The 'class_exists` check is just a hack suggestion to prevent the re-declaration of a pre-existing class. This check needs to be placed within the class performing "eval."
Pestilence
PHPUnit is calling the eval code, not my own code. I think it has something to do with when it parses the WSDL and creates mock objects off of that definition.
scraton
A: 

Why are you creating the mock in setUp() if the point is to obtain mock class definition once per execution of whole test file? If I remember correctly it's run before each test defined in "this" test class... Try setUpBeforeClass()

From http://www.phpunit.de/manual/3.4/en/fixtures.html

In addition, the setUpBeforeClass() and tearDownAfterClass() template methods are called before the first test of the test case class is run and after the last test of the test case class is run, respectively.

eyescream
A: 

PHPUnit creates a class for the mock based on the WSDL. The classname, if not provided, is constructed from the .wsdl filename, so it's always the same. Across tests, when it tries to create again the class, it crashes.

The only thing you need is add to the Mock definition a classname of your own, so PHPUnit does not create an automatic name, notice the second argument to $this->getMockFromWsdl:

protected function setUp() {
   parent::setUp();

   $this->client = new Client();

   $this->SoapClient = $this->getMockFromWsdl(
      'service.wsdl', 'MyMockClass'
   );

   $this->client->setClient($this->SoapClient);
}

You can now create as many clients as you want, only change the classname for each one.

Mandos78