5

We use Varien_Http_Client to make http requests from a Magento extension, like this:

public function callApi(…)
{
    <SNIP>

    // Set default factory adapter to socket in case curl isn't installed.
    $client = new Varien_Http_Client($apiUri, array(
        'adapter' => 'Zend_Http_Client_Adapter_Socket',
        'timeout' => $timeout, 
    ));
    $client->setRawData($xmlDoc->saveXML())->setEncType('text/xml');
    $response = $client->request($method);
    $results = '';
    if ($response->isSuccessful()) {
        $results = $response->getBody();
    }
    return $results;
}

I understand I should avoid testing the internals of Varien_Http_Client; rather, I should test that we are sending it the right inputs, and handling its outputs correctly. I can mock Varien_Http_Client easily enough, but even if I refactor this code to let me replace the Varien_Http_Client with its mock, I don't understand how to generally* test that the constructor was called with the expected arguments, since the constructor is called by PHPUnit::getMock.

I don't need a mock object; I need a mock class. How can I test that a constructor was called with expected arguments?

* (In this case I know ways to work around this problem specific to Varien_Http_Client, but what can I do with more opaque third-party code?)

1 Answer 1

4

This is what we call "untestable" code. When you build dependencies inside your methods, there is no way to mock them. Every use of "new" keyword in your model is a signal that you should consider injecting the object instead of create it inside. In my opinion, the only exception from that rule is when you create a "data container" object or factory class. But in these cases probably you can test the object because methods will return it.

So as you said, the method you showed need a little refactor, for example:

class ApiClass
{
    protected $client;

    public function __construct(Varien_Http_Client $client)
    {
        $this->client = $client;
    }

    public function callApi()
    {
        $this->client->setRawData($xmlDoc->saveXML())->setEncType('text/xml');

        (...)

Best!

Sign up to request clarification or add additional context in comments.

2 Comments

Thanks! But I'm not out of the woods yet. I have to instantiate the Varien_Http_Client somewhere, and wherever that happens, I'll probably want to test, right? How do I avoid it again down the line?
Good point. Of course there always is some place where you need create a service inside a class. That's why I've write that you should just consider injecting. In practice you can minimize these places and limit them by for example make one class responsible for creating dependencies, like service container pattern. In practice also I admit directly creating dependencies in upper layers of the app, for example controller in MVC and presenter in MVP

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.