可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
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); }
回答1:
This isn't an ideal solution, but in your setup give the SOAP mock a "random" class name, for example
$this->_soapClient = $this->getMockFromWsdl( 'some.wsdl', 'SoapClient' . md5( time().rand() ) );
This ensures that at least when the setup is called you don't get that error.
回答2:
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); } ?>
回答3:
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.
回答4:
Adding my $.02 here.. I recently ran across this same situation and after some frustration here is how I was able to solve it:
class MyTest extends PHPUnit_Framework_TestCase protected static $_soapMock = null; public function testDoWork_WillSucceed( ) { $this->_worker->setClient( self::getSoapClient( $this ) ); $result = $this->_worker->doWork( ); $this->assertEquals( true, $result['success'] ); } protected static function getSoapClient( $obj ) { if( !self::$_soapMock ) { self::$_soapMock = $obj->getMockFromWsdl( 'Test/wsdl.xml', 'SoapClient_MyWorker' ); } return self::$_soapMock; } }
I have many 'workers', each in their own test class and each of which needs access to a mocked SOAP object. In the second parameter to getMockFromWsdl
I had to make sure I was passing each a unique name (e.g. SoapClient_MyWorker
) or it would bring PHPUnit crashing down.
I am not sure whether or not getting the SOAP mock from a static function and getting access to the getMockFromWsdl
function by passing in $this
as a parameter is the best way to accomplish this, but there ya go.
回答5:
One way to pass an object from test to test in PHPUnits is with test dependency, if instantiating a particular object is too taxing/time consuming:
<?php /** * Pass an object from test to test */ class WebSericeTest extends PHPUnit_Framework_TestCase { protected function setUp() { parent::setUp(); // I don't know enough about your test cases, and do not know // the implications of moving code out of your setup. } /** * First Test which creates the SoapClient mock object. */ public function test1() { $this->client = new Client(); $soapClient = $this->getMockFromWsdl( 'service.wsdl' ); $this->client->setClient($this->SoapClient); $this->markTestIncomplete(); // To complete this test you could assert that the // soap client is set in the client object. Or // perform some other test of your choosing. return $soapClient; } /** * Second Test depends on web service mock object from the first test. * @depends test1 */ public function test1( $soapClient ) { // you should now have the soap client returned from the first test. return $soapClient; } /** * Third Test depends on web service mock object from the first test. * @depends test1 */ public function test3( $soapClient ) { // you should now have the soap client returned from the first test. return $soapClient; } } ?>
回答6:
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.