Running SauceLabs Selenium test suite locally with PHPUnit

SauceLabs provide a great hosted Selenium service for cross-browser testing in the cloud, which allows you to run a Selenium test suite against multiple browsers using the SauceLabs API and SauceConnect.

They also provide integration with PHPUnit, so that you can plug your SauceLabs Selenium tests directly into your PHPUnit test suite.

Sometimes, though, you want to run the same Selenium test suite against a single browser on your local machine, before you run the full thing against all the browsers on SauceLabs.

PhpUnit Mocks that suddenly stopped working

We had some test code that created a mock for a Store class, and gave it a method to mock out:

$mockStore = $this->getMock(‘Store’, array(‘save’));

That seemed to work fine in the tests, we could setup expectations, the correct return values got returned, and so on.

Then, in some unrelated code, we changed some require_once statements, and suddenly the test with the mocks stopped working – it threw exceptions trying to create the Store mock.

It turns out that the constructor for the Store class needed some parameters, and the mock framework needs you to supply those parameters when you create the mock, because it will call the real constructor behind the scenes.

The mock had been working OK previously because the real Store class hadn’t been loaded anywhere by the test – but then the changes to some require_once statements elsewhere in the code meant that the Store class WAS now loaded, and instantiated by the call to create a mock.

So the mock framework in PhpUnit will create a mock for you even if it has no idea what class it is that you’re trying to mock out – you could do

$mockStore = $this->getMock(‘foo’, array(‘save’));

and it would still give you a mock that worked fine. If it DOES find a class of that name, though, it’ll instantiate it.

In fact, this behaviour is probably a good thing for TDD – it means you can create the mocks you need before you’ve ever created the real classes. You just have to be aware that when you DO create the real class, the mocks will start being created based on the real thing.

The solution in our test was to use the flag that tells the mock framework not to call the original constructor – it’s a bit more clumsy, because you also have to supply some other additional parameters: