Don't wet what you don't own

Approx. translator: the rule itself is quite old, and the example given in the article is, in my opinion, the simplest. Therefore, the article is more suitable for beginners, people with good experience in writing autotests may not find anything new for themselves. UPD. I still do not think that the author proposes to wrap all the APIs of the framework with which you are working with his own layer (well, it would be extremely strange), but rather it is about loosely structured / typed general-purpose classes, like HttpRequest from the example in the article.





Web applications are often designed to handle HTTP requests. Objects are commonly used to encapsulate request data. Depending on the framework, we might have an interface like





interface HttpRequest
{
    public function get(string $name): string;

    // ...
}
      
      



or even a specific class like





class HttpRequest
{
    public function get(string $name): string
    {
        // ...
    }

    // ...
}
      
      



which we can (and should) use to access the request data.





Symfony, for example, has Symfony \ Component \ HttpFoundation \ Request :: get (). As an example, we will not worry about what type of HTTP request we are handling (GET, POST, or other). Instead, let's focus on implicit APIs like HttpRequest :: get () and the problems they pose.





, , , get() , . . get():





class SomeController
{
    public function execute(HttpRequest $request): HttpResponse
    {
        $id     = $request->get('id');
        $amount = $request->get('amount');
        $price  = $request->get('price');

        // ...
    }
}
      
      



, action- (: (eng )). , HTTP-.





HttpRequest (stub) mock- SomeController , get() , : 'id', 'amount' 'price'.





, , action- .





SomeController HttpRequest (stub) unit PHPUnit :





$request = $this->createStub(HttpRequest::class);

$request->method('get')
        ->willReturnOnConsecutiveCalls(
              '1',
              '2',
              '3',
          );

$controller = new SomeController;

$controller->execute($request);
      
      



SomeController HttpRequest, mock-, :





$request = $this->createMock(HttpRequest::class);

$request->expects($this->exactly(3))
        ->method('get')
        ->withConsecutive(
            ['id'],
            ['amount'],
            ['price']
        )
        ->willReturnOnConsecutiveCalls(
            '1',
            '2',
            '3',
        );

$controller = new SomeController;

$controller->execute($request);
      
      



, , , ( . ).





, HttpRequest::get() : «id», «amount» , , «price».





SomeController::execute(), HttpRequest::get(), . , . .





, HTTP-, API, , HTTP, get(). , , , : HttpRequest , .





« , » « , ». 2009 « - »:





« , , , , , . , , , , , ».





, , ? :





« [...] , , - , , . [...], - API [...] "





:





interface SomeRequestInterface
{
    public function getId(): string;

    public function getAmount(): string;

    public function getPrice(): string;
}
      
      



, , value-. .





SomeRequestInterface :





$request = $this->createStub(SomeRequestInterface::class);

$request->method('getId')
        ->willReturn(1);

$request->method('getAmount')
        ->willReturn(2);

$request->method('getPrice')
        ->willReturn(3);
      
      



, HTTP- , - HTTP- . . HTTP- . . :





class SomeRequest implements SomeRequestInterface
{
    private HttpRequest $request;

    public function __construct(HttpRequest $request)
    {
        $this->request = $request;
    }

    public function getId(): string
    {
        return $this->request->get('id');
    }

    public function getAmount(): string
    {
        return $this->request->get('amount');
    }

    public function getPrice(): string
    {
        return $this->request->get('price');
    }
}
      
      



:





class SomeController
{
    public function execute(HttpRequest $request)
    {
        return $this->executable->execute(
            new SomeRequest($request)
        )
    }
}
      
      



SomeController , , HTTP .





You will, of course, have to make your request wrapper specific to each controller. Does your code need specific headers? Create a method to just get them. Does your code need an uploaded file? Create a method to get exactly this.





A full HTTP request can contain headers, values, maybe uploaded files, POST body, etc. Setting up a test stub or mock for all of this while you don't own the interface prevents you from getting the job done. Defining your own interface greatly simplifies the task.








All Articles