Search code examples
phpunit-testingmockingzend-framework2phpunit

PHPUnit testing constructorless class


I'm developing unit tests on a project and I came across a class that does not contain constructor.

You may ask yourself, "How does this object exist then?"

Well, in another system, this object is generated and saved in database, so in the system where unit tests are required, there is no constructor defined for that class. But there is a need to test their functions so that if they are modified in the system in the future, the tests will indicate in which situations they are used, this will ensure me not to neglect the other points of the project that use it.

Within this environment, how can you test this type of class?

I tried using mocks, but mock brings all null attributes. In addition, a mocked object only comes as a result of a function that you say previously it will bring. So to test the functions of the object itself, this is not functional.

Any idea?

PS: Sorry for the bad english.


Solution

  • Constructors are optional. A class does not need a constructor for you to be able to instantiate it or test whether its methods are functioning correctly.

    As I understand it, you want to test a method which behaves differently depending on a particular property, which would normally be set by a constructor but in your class isn't. That means that in the actual code usage, that property is probably being set directly at some point or there's a different method that sets its value.

    In general, for testing these kinds of methods you should always set such a property yourself anyway. The reason for this is simple: a single test should only test one particular method. If you rely on the constructor, your test would be testing the combination of both the constructor and that method. Your test of the method will become dependent on the constructor behaving properly.

    Imagine the following:

    class Mood {
        public $happy;
    
        function __construct() {
            $this->happy = true;
        }
    
        public function greet() {
            if ($this->happy) {
                return 'Hi!';
            } else {
                return 'Go away';
            }
        }
    }
    

    Let's say you want to test the behavior of the greet() method:

    class MoodTest extends \PHPUnit\Framework\TestCase {
        public function testGreet() {
            $mood = new Mood();
            $this->assertEqual('Hi!', $mood->greet())
        }
    }
    

    This test would pass, because we assume the constructor is doing its job and is setting the $happy property to true. But there are two problems here:

    1. We are not testing that the method works properly if $happy is false
    2. We rely on the constructor to set $happy's initial value to true

    This means things can change outside our control that would break this specific test, even though the function still works as expected. Business logic may change so that the constructor will set $happy to false initially. Or the developer's logic may change where the constructor disappears entirely and $happy is set in some other way (maybe a setHappy() method is introduced at some point).

    To properly test the greet() method, there should be a test for every possible outcome. In this case the method has a single if statement, so there ought to be a test case for both outcomes of that condition:

    class MoodTest extends \PHPUnit\Framework\TestCase {
        public function testGreetIfHappy() {
            $mood = new Mood();
            $mood->happy = true;
            $this->assertEqual('Hi!', $mood->greet())
        }
    
        public function testGreetIfNotHappy() {
            $mood = new Mood();
            $mood->happy = false;
            $this->assertEqual('Go away', $mood->greet())
        }
    }
    

    Now, no matter what happens to the constructor or business logic, these tests are testing only the behavior of the greet() method. Whatever else the constructor is doing (even if doesn't exist or does nothing at all) no longer has any effect on the tests of the greet() method.