skip to Main Content

I am writing some unit test for some legacy code as a preparation for some refactoring. One section of the code is calling a class from a library where I cannot change the code in it. The code is using its constructor and calling some static variable. May I ask how can I mock the method for testing? (or how do I test the method without mocking?)

I have tried to simplify my code for an example:

<?php
//This is from the library code that I am unable to change
class ResponseMessage {
    const STATUS_ERROR = 500;
    const STATUS_SUCCESS = 200;

    function __construct( $responseMessage, $responseCode ) {
        $this->responseMessage = $responseMessage;
        $this->responseCode = $responseCode
    }
    
    public function sendMessage($sendToAdmin = false) {
        if ($sendToAdmin) {
            $targetEmailAddress = $this->emailAddresses['admin'];
            $targetAPISettings = $this->apiSettings['admin'];
        }
        else {
            $targetEmailAddress = $this->emailAddresses['customer'];
            $targetAPISettings = $this->apiSettings['customer'];
        }
        $this->tirggerSendEmail($targetEmailAddress);
        $this->triggerAPI($targetAPISettings);
    }
    
    //.... Some other methods and variables
}

///////////////////////////////////////////////////////////////////
//This is my class to be tested. If possible I would like to avoid changing any code, but if it is necessary this code can be changed a bit for injecting mock class.
class myClass {
    // ... some other methods where I have no problem testing....
    function handle($input) {
        //The actual comparision is much more complicated. Simplified for this question
        if (empty($input)) {
            // Some other error handling, no response message is generated
            return;
        }
        if ( $input == 1 )
            $responseMessage = new ResponseMessage('The input is 1', ResponseMessage::STATUS_SUCCESS );
        else if ( $input == 2 )
            $responseMessage = new ResponseMessage('The input is 2', ResponseMessage::STATUS_SUCCESS );
        else
            $responseMessage = new ResponseMessage('Unexpected input', ResponseMessage::STATUS_ERROR );
        
        $respnoseMessage->sendMessage();

        $responseMessageToAdmin = new ResponseMessage('The comaparison is triggered', RespnoseMessage::STATUS_SUCCESS);
        $responseMessageToAdmin->sendMessage(true);
    }
}
///////////////////////////////////////////////////////////////////
//This is my testing class in PHPUnit.
class testMyClass {
    public function testProcessWithInputOne {
        // how should I mock ResponseMessage here?
        /////////////////////////////////////////////////////////
        // These are not going to work, but tried to write some code to demo what I want to test.
        $mockResponseMessage = Mockery::mock('alias:ResponseMessage');//used alias for simplicity, but mocking it normally and inject the mocked object is also acceptable.
        $mockResponseMessage->setConst('STATUS_SUCCESS', 200);
        $mockResponseMessage->shouldReceive('__construct')->with('The input is 1', 200)->once()->andReturn($mockResponseMessage);
        $mockResponseMessage->shouldReceive('sendMessage')->with(false)->once(); //here with(false) is testing no argument is passed as the default argument value is false
        
        $mockResponseMessage->shouldReceive('__construct')->with('The comaparison is triggered', 200)->once()->andReturn($mockResponseMessage);
        $mockResponseMessage->shouldReceive('sendMessage')->with(true)->once();
        /////////////////////////////////////////////////////////
        $myClass = new MyClass();
        $myClass->handle(1);
    }
}

2

Answers


  1. If you can modify MyClass, I would do the following. The challenge is that ResponseMessage needs values in the constructor and therefore cannot be mocked the way MyClass is structured.

    The ResponseMessageAdapter is more or less a bridge to the ResponseMessage.

    You pass the ResponseMessageAdapter to MyClass via the constructor. You can also use it outside the test.

    ResponseMessageAdapter::getResponseMessage() returns the instance of ResponseMessage. So you can mock all the methods in ResponseMessage.

    class ResponseMessageAdapter {
    
        private string $responseMessage;
        private int $responseCode;
    
        public function setResponseMessage(string $responseMessage): void
        {
            $this->responseMessage = $responseMessage;
        }
    
        public function setResponseCode(int $responseCode): void
        {
            $this->responseCode = $responseCode;
        }
    
        public function getResponseMessage(): ResponseMessage
        {
            return new ResponseMessage(
                $this->responseMessage,
                $this->responseCode
            );
        }
    }
    
    class MyClass {
    
        private ResponseMessageAdapter $responseMessageAdapter;
    
        public function __construct(ResponseMessageAdapter $responseMessageProxy)
        {
            $this->responseMessageAdapter = $responseMessageProxy;
        }
    
        public function handle($input)
        {
            if ($input == 1) {
                $this->responseMessageAdapter->setResponseMessage('The input is 1');
                $this->responseMessageAdapter->setResponseCode(ResponseMessage::STATUS_SUCCESS);
                $responseMessage = $this->responseMessageAdapter->getResponseMessage();
                $responseMessage->sendMessage();
            }
            // ...
        }
    }
    
    Login or Signup to reply.
  2. class testMyClass extends PHPUnitFrameworkTestCase {
    public function testProcessWithInputOne() {
        // Create a mock of the ResponseMessage class
        $mockResponseMessage = $this->getMockBuilder(ResponseMessage::class)
            ->disableOriginalConstructor()
            ->getMock();
    
        // Set up expectations for the mock object
        $mockResponseMessage->expects($this->once())
            ->method('sendMessage')
            ->with($this->equalTo(false));
    
        // Replace the original ResponseMessage class with the mock
        $this->getMockBuilder('ResponseMessage')
            ->setMockClassName('ResponseMessage')
            ->setMockObjectConstructorArgs(['The input is 1', ResponseMessage::STATUS_SUCCESS])
            ->getMock();
    
        // Create an instance of myClass and call the handle() method
        $myClass = new myClass();
        $myClass->handle(1);
    }
    

    }

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search