I read somewhere that it's a good idea to break methods down into smaller, testable functions so that the smaller methods can be tested. But I'm confused on how to test the method that calls the smaller method. Here's an example:
class MyTestableClass
{
public function myHugeFunction($list, array $list_filter_params)
{
// do a bunch of stuff, then...
foreach($list as $item) {
$this->subFunction($item);
}
}
public function subFunction()
{
// do stuff for each item
}
}
and the test class:
class MyTestableClassTest extends PHPUnit_Framework_TestCase
{
public function testSubFunction
{
// This tests the smaller, bite-size method, which is good.
}
public function testMyHugeFunction
{
// this tests the parent function *and* the subFunction. I'm trying
// to test *just* the parent function.
}
}
I know how to test the subFunction, but since I can't stub a method in the same class, I don't know how to test the parent method only. I'd like to find a way of somehow stubbing the subFunction to just return true.
Do you use Events and stub the event class? That's the only way I can think of to stub another method called in the same class.
Apart from things what #fab said in his comment (which you really should consider!), actually you can stub / mock methods in the SUT. For your example, build your SUT object may looks like:
class MyTestableClassTest extends PHPUnit_Framework_TestCase
{
public function testSubFunction
{
// This tests the smaller, bite-size method, which is good.
}
public function testMyHugeFunction
{
// first, prepare your arugments $list and $list_filter_params
// (...)
// Next build mock. The second argument means that you will mock ONLY subFunction method
$this->sut = $this->getMock('Namespace\MyTestableClass', array('subFunction'));
// now you can set your subFunction expectations:
$this->sut->expects($this->exactly(count($list))
->with($this->anything()); // of course you can make this mock better, I'm just showing a case
// start testing
$this->sut->myHugeFunction($list, $list_filter_params);
}
}
PS And again, as #fab said: if you show a specific case, I'm sure you'll get a lot of good answers from people here.
Related
I have a class method I am trying to write a unit test for, it starts off something like this:
public function determineStatusActivity($proposal, $decisionStatusId): ?array
{
if (isset($proposal->contact_org_id)) {
$this->personId = PipedriveActivityHelper::getPersonId($proposal->contact_org_id);
}
I then have my test method which will need the response to run the check:
public function acceptedActivityTest()
{
$activity = new CreateActivityInPipedriveForProposalStatusChange();
$response = $activity->determineStatusActivity($this->proposal, 1);
//Compare the response to a defined stub..
}
This class method determineStatusActivity() receives two pieces of data that I can create mocks for a pass to it, however I am at a loss at what to do when it gets to the static PipedriveActivityHelper class, is there a way to define the static class' behaviour in the test file method?
I believe this is what you are looking for, notice the test class decorators. TDD is always the best approach.
I can not set value for inner method when I try to test. Here I have written a sample class. I have created mock object for same class but does not effect.
class A
{
public function OneTest()
{
if($this->TwoTest()){
return true;
}
}
public function TwoTest()
{
// return somethings
}
}
I am new at phpunit test writing. if some one expert help me that good for me. I want to test this method. I have tried with:
class ATest extends \PHPUnit_Framework_TestCase
{
public function testOne()
{
$helper = new testOne();
// trying to set TwoTest() method value but does not effect.
$mock = $this->createMock(A::class);
$mock->method("TwoTest")
->willReturn(true);
$this->assertTrue($helper->OneTest();
}
}
Actually I do not know how to use my mocking method result. My actual implementation in twoTest method contains some db related code. I do not want to run db code in testing time.
You are pretty close with your mock. What you want to do is called partial mocking. This is done by creating a mock of A with only TwoTest being mocked, i.e. it will now always return true and never actually call the real code inside the original implementation in A, whereas all other methods still act as before. Therefore calling $mock->OneTest() should return the expected result. Since you make both calls on the (partially) mocked instance, you won't need $helper. So your test would probably look something like this:
public function testOneWhenTwoTestReturnsTrue()
{
$mock = $this->getMockBuilder(A::class)
->setMethods(["TwoTest"])
->getMock();
$mock->method("TwoTest")
->willReturn(true);
$this->assertTrue($mock->OneTest();
}
Notice that I use getMockBuilder() instead of just createMock() and setMethods() is what we need for your test. We only overwrite the one method we want to mock, the rest will behave as defined in the original class. To quote the docs:
setMethods(array $methods) can be called on the Mock Builder object to specify the methods that are to be replaced with a configurable test double. The behavior of the other methods is not changed. If you call setMethods(null), then no methods will be replaced.
Say I want to replace a method in an object that gets database from a database with one that has the data pre-populated. How would I do this?
According to https://phpunit.de/manual/current/en/test-doubles.html ...
setMethods(array $methods) can be called on the Mock Builder object to
specify the methods that are to be replaced with a configurable test
double. The behavior of the other methods is not changed. If you call
setMethods(NULL), then no methods will be replaced.
Great. So that tells phpunit which methods I want to replace but where do I tell it what I'm replacing them with?
I found this example:
protected function createSSHMock()
{
return $this->getMockBuilder('Net_SSH2')
->disableOriginalConstructor()
->setMethods(array('__destruct'))
->getMock();
}
Great - so the __destruct method is being replaced. But what is it being replaced with? I have no idea. Here's the source for that:
https://github.com/phpseclib/phpseclib/blob/master/tests/Unit/Net/SSH2Test.php
With a method that doesn't do anything, but whose behaviour you can configure later. Although I'm not sure you fully understood how mocking works.
You're not supposed to mock the class you're testing, you're supposed to mock objects on which the class being tested relies on. For example:
// class I want to test
class TaxCalculator
{
public function calculateSalesTax(Product $product)
{
$price = $product->getPrice();
return $price / 5; // whatever calculation
}
}
// class I need to mock for testing purposes
class Product
{
public function getPrice()
{
// connect to the database, read the product and return the price
}
}
// test
class TaxCalculatorTest extends \PHPUnit_Framework_TestCase
{
public function testCalculateSalesTax()
{
// since I want to test the logic inside the calculateSalesTax method
// I mock a product and configure the methods to return some predefined
// values that will allow me to check that everything is okay
$mock = $this->getMock('Product');
$mock->method('getPrice')
->willReturn(10);
$taxCalculator = new TaxCalculator();
$this->assertEquals(2, $taxCalculator->calculateSalesTax($mock));
}
}
Your test mocks the exact class you're trying to test, which might be a mistake, since some methods might be overridden during mocking.
I'm trying to create a pretty standard unit test where I call a method and assert it's response, however the method I'm testing calls another method inside the same class which does a little bit of heavy lifting.
I want to mock that one method but still execute the method I'm testing as is, only with the mocked value returned from the call to the other method.
I've dumbed down the example to make it as simple as possible.
class MyClass
{
// I want to test this method, but mock the handleValue method to always return a set value.
public function testMethod($arg)
{
$value = $arg->getValue();
$this->handleValue($value);
}
// This method needs to be mocked to always return a set value.
public function handleValue($value)
{
// Do a bunch of stuff...
$value += 20;
return $value;
}
}
My attempt at writing the tests.
class MyClassTest extends \PHPUnit_Framework_TestCase
{
public function testTheTestMethod()
{
// mock the object that is passed in as an arg
$arg = $this->getMockBuilder('SomeEntity')->getMock();
$arg->expects($this->any())
->method('getValue')
->will($this->returnValue(10));
// test handle document()
$myClass = new MyClass();
$result = $myClass->testMethod($arg);
// assert result is the correct
$this->assertEquals($result, 50);
}
}
I have tried mocking the MyClass object, but when I do that and call the testMethod it always returns null. I need a way to mock the one method but leave the rest of the object intact.
You can mock the class that you are testing and specify the method that you want to mock.
$mock = $this->getMockBuilder('MyClass')
->setMethods(array('handleValue'))
->getMock();
$mock->expects($this->once())
->method('handleValue')
->will($this->returnValue(23)) //Whatever value you want to return
However, IMO this is not the best idea for your tests. Testing like this will make refactoring much more difficult. You are specifying the implementation of the class rather than the behavior that the class is supposed to have. If handleValue is doing a lot of complicated work that makes testing difficult, consider moving the logic into a separate class and injecting that into your class. Then you can create a mock of that class and pass it in to testMethod. Doing so will give you the added advantage of making MyClass more extensible if handleValue needs to adapt its behavior.
http://www.oodesign.com/strategy-pattern.html
As a general rule, you should not mock the system that you are testing.
You can specify which methods to mock (partial mock) with setMethods():
// Let's do a `partial mock` of the object. By passing in an array of methods to `setMethods`
// we are telling PHPUnit to only mock the methods we specify, in this case `handleValue()`.
$csc = $this->getMockBuilder('Lightmaker\CloudSearchBundle\Controller\CloudSearchController')
->setConstructorArgs($constructor)
->setMethods(array('handleValue'))
->getMock();
// Tell the `handleValue` method to return 'bla'
$csc->expects($this->any())
->method('handleValue')
->with('bla');
Any other methods in the class not specified in the array you give setMethods() will be executed as is. If you do not use setMethods all methods will return NULL unless you specifically set them.
I have a class that looks something like this:
class Foo {
protected $_arr = array();
public function has($key) {
return array_key_exists($key, $this->_arr);
}
public function add($key, $val) {
$this->_arr[$key] = $val;
}
}
For my PHPUnit tests for these methods, the only way I can think if to test add() is by asserting that has() returns TRUE for the same key after adding it. This makes my testAdd() test dependent on my testHas() test. Conversely, the only way I can think of to test has() is basically doing the exact same steps, but this would make this test dependent on an already dependent test, producing a chicken and egg type problem.
Am I going about this the wrong way? What's a better method for testing stuff like this?
Instead of writing one-test-per-method, design your tests around the functionality the class must provide. You'll have tests that exercise multiple methods, but that's fine because it indicates to the developer that those methods are related. This is where Test Driven Development--where you write the tests while designing the contract for the class before writing the code for the class--really shines.
class FooTest extends PHPUnit_Framework_TestCase
{
function testStartsEmpty() {
$foo = new Foo;
self::assertFalse($foo->has('bar'));
}
function testAddStoresKeys() {
$foo = new Foo;
$foo->add('bar', 'baz');
self::assertTrue($foo->has('bar'));
}
function testAddStoresKeysWithNullValues() {
$foo = new Foo;
$foo->add('bar', null);
self::assertTrue($foo->has('bar'));
}
function testAddStoresValues() {
$foo = new Foo;
$foo->add('bar', 'baz');
self::assertEquals('baz', $foo->get('bar')); // fatal error
}
}
Now that testAddStoresValues() fails, it's time to implement a method to get the value for a given key: Foo::get($key).
PHPUnit allows testing of non-public members.
However, using $sut->has() to find out whether $sut->add() worked is perfectly fine. Also, when you test $sut->add(), you dont need to write a separate test for $sut->has() as well, because it's covered in the $sut->add() test already. Just add a #covers annotation.
In reference to the long dicussions in the comments to #Gordon s answer:
The unit in unit testing is the object not the method! You don't want to test methods in isolation from the other methods of the objects. (If you wanted to do so just use functions ;) ) - It's really important to test how the object behaves and if it does the expected think when called and not how the methods interact internally.
I've recently written a blog post explaining why it is important to not test single methods in isolation:
http://edorian.posterous.com/the-unit-in-unit-testing
Quoting from my post:
Unit testing, in PHP, is about testing the observable behaviors of a class
Most importantly:
Observable from the outside! Nobody cares about the internal state of a class if it never changes the outcome of a method call.
Take this sample:
public function setValue($value) {
$this->value = $value;
}
public function execute() {
if (!$this->value) {
throw new Exception("No Value, no good");
}
return $value * 10; // business logic
}
It sounds trivial but the distinction is important and more easy to overlook when looking at bigger classes.
What do we test there?
IF we don’t setValue AND then call execute an exception is thrown!
IF we DO setValue AND then call execute we get an computed result
So we are testing two behaviors of your class and not the methods in isolation!
IMO you can test behavior of the object. So you can test if has return false before and true after adding some stuff into collection.