I think the problem you are having is that you are missing a setup that ensures the right test outcome. That may sound a bit odd, but consider this scenario
class ConnectedResource
{
private $connector;
public function __construct($connector)
{
$this->connector = $connector;
}
public function connectToDevice()
{
try {
$this->connector->connect();
} catch (\Exception $e) {
return false;
}
return true;
}
}
That is really simplified, but the essential bit is that you want to test both paths: connect() success and the Exception.
What you would do in your test now is either
public function testSuccessfulConnectReturnsTrue()
{
$connector = new Connector(
// config to make a successful connection
);
$myObject = new ConnectedResource($connector);
$this->assertTrue($myObject->connectToDevice());
}
public function testFailedConnectReturnsFalse()
{
$connector = new Connector(
// invalid config that will raise an exception
);
$myObject = new ConnectedResource($connector);
$this->assertFalse($myObject->connectToDevice());
}
Now both tests will work, because they use different connections (one that works and one that doesn't).
The other possibility is to pass in a Mock-Connector. In other words instead of a real connector you create a dummy object where you can decide yourself what $this->connector->connect()
would return. Think of it as something along the lines of "assuming I have a connector that returns true then ConnectedResource should behave like this". The code for this could look something like this:
public function testSuccessfulConnectReturnsTrue()
{
$connector = $this->createMock(Connector::class);
$connector->expect($this->any())
->method('connect')
->willReturn(true)
;
$myObject = new ConnectedResource($connector);
$this->assertTrue($myObject->connectToDevice());
}
and for the failure scenario it would look something like this:
public function testFailedConnectReturnsFalse()
{
$connector = $this->createMock(Connector::class);
$connector->expect($this->any())
->method('connect')
->willReturn($this->throwException(new Exception))
;
$myObject = new ConnectedResource($connector);
$this->assertTrue($myObject->connectToDevice());
}
Now you control everything outside the scope of the test (the connection) and only test the behavior defined inside connectToDevice()
. This will make your test safe against changes in the other class, but could cause problems when for example in a future update the connect() function changes, e.g. arguments are added or changed.
The important bit is, that you have to ensure the requirements for runnning your test are fulfilled.
connectToDevice
) it should return/do what I defined, then its test succeeds. So the test it's not strictly correlated to a function success rather to a return or an action that happens when a function is called. As I said, I'm at beginning with unit tests, so I don't know if my argumentation is right.connectToDevice
in two test functions without changing the environment. This two functions are opposite => you will always have a test that fails. To change that you have in one of the functions to manipulate the environment and then you will have always the test to be successful.