Why testing API Platform PUT and PATCH requests does not work but API works with same operations with custom components with DTO

I use Doctrine objects in database that are mapped to DTOs by services with a getComponentDtoFromEntity($component) method to expose them on API Platform 3.2 on Symfony 7.1 and PHP 8.3. Each DTO has a route with needed HTTP verbs which allows me to work only with data I need, have multiple endpoints using the same entity (with different DTO), having composite DTOs (a mix of multiples entities) and never expose directly my entities.
I created some DTO directly linked to API Platform, for example :

#[ApiResource]
#[Get(
    uriTemplate: 'stock/components/{id}',
    read: true,
    provider: ComponentDtoProviderService::class,
)]
#[GetCollection(
    uriTemplate: 'stock/components/',
    provider: ComponentDtoProviderService::class
)]
#[Post(
    uriTemplate: 'stock/components/',
    validationContext: ['groups' => ['Default', 'postValidation']],
    processor: ComponentDtoProcessorService::class,
)]
#[Delete(
    uriTemplate: 'stock/components/{id}',
    provider: ComponentDtoProviderService::class,
    processor: ComponentDtoProcessorService::class
)]
#[Put(
    uriTemplate: 'stock/components/',
    validationContext: ['groups' => ['Default']],
    processor: ComponentDtoProcessorService::class
)]
readonly class ComponentDto
{
    public function __construct(
        public ?string $id,
        #[AssertNotBlank(groups: ['postValidation'])]
        public string $name,
        public ?string $ean13,
        public ?int $quantity,
        public ?int $quantityThreshold,
    ) {
    }
}

When I use the built-in API test at /api address, everything works fine with every operation (GET, POST, PUT, DELETE).

I use PHPunit to test my API, so I created tests for get, getAll, post, delete and put/patch operations using previously added fixtures (that I get from name).

    public function testComponentsPut(): void
    {
        $componentBlueRose = $this->componentRepository->findByName(ComponentFixtures::COMPONENT_BLUE_ROSE);
        self::assertNotNull($componentBlueRose);

        // test new EAN13 and quantity and quantityThreshold
        $componentBlueRoseDtoModified = new ComponentDto(
            $componentBlueRose->getId()->toString(),
            $componentBlueRose->getName(),
            '9234569990123', // data are modified here
            400, // data are modified here
            8, // data are modified here
        );

        $response = parent::createClient()->request('PUT', 'api/stock/components/',
            ['json' => $componentBlueRoseDtoModified]);

        self::assertResponseIsSuccessful();
        $componentBlueRoseUpdated = $this->componentRepository->findByName(ComponentFixtures::COMPONENT_BLUE_ROSE);

        // todo : its not working here
        self::assertSame($componentBlueRoseDtoModified->ean13, $componentBlueRoseUpdated->getEan13());
        self::assertSame($componentBlueRoseDtoModified->quantity, $componentBlueRoseUpdated->getQuantity());
        self::assertSame($componentBlueRoseDtoModified->quantityThreshold, $componentBlueRoseUpdated->getQuantityThreshold());
}

Previous tests are made with Get, Post, Delete and GetAll and everything works fine, but not in the case of PUT and PATCH : when I get the saved data in database, the data is never modified. To test, I take an object in database, I map it in a DTO, then I modify the DTO and send it in API with PUT verb (I also did the same with PATCH). Then, I get the object with same ID in database and I compare both objects properties to check that modifications have been saved. But they are not.

This made me discovering a strange behavior of API Platform :
When PATCH or PUT operations are called, the Provider is always called even if it’s not specified, and it’s called BEFORE the processor witch, in fact does :

  • The DTO with new modifications is passed to the HTTPRequest like this :

          $response = $client->request('PUT', 'api/stock/components/',
          ['json' => $componentBlueRoseDtoModified]);
    
  • This DTO is intercepted by the Provider class which takes the ID, load a new object from the database and map it as DTO, which is the correct behavior of GET operation. The DTO returned by the Provider is from database so it’s not the modified DTO given in HTTP request.

  • This DTO is given to the Processor which saves it.

The problem is that the Processor saves the DTO taken from the database and not the one given in the first place, and modifications are never saved.

Since I know that when /{id} is specified, a Provider is required for Patch and Put operations (in recent Api Platform versions), I tried both paths :

  • /api/component/{id} with specified Provider and Processor and
  • /api/component/ with no Provider
    It just works the same way : testing directly with /api works but not in unit tests.

Here is the Provider code :

public function provide(Operation $operation, array $uriVariables = [], array $context = []): array|ComponentDto
    {
        // get all components
        if ($operation instanceof CollectionOperationInterface) {
            $components = $this->componentRepository->getAll();
            $componentDtos = [];
            foreach ($components as $component) {
                $componentDtos[] = $this->getComponentDtoFromEntity($component);
            }

            return $componentDtos;
        }

        // get one component
        if (!isset($uriVariables['id'])) {
            throw new Exception('Id is required');
        }
        $component = $this->componentRepository->findByIdAsString($uriVariables['id']);

        if (null === $component) {
            throw new Exception('Component not found');
        }

        return $this->getComponentDtoFromEntity($component);
    }

    public function getComponentDtoFromEntity(Component $component): ComponentDto
    {
        return new ComponentDto(
            $component->getId() === null ? null : (string) $component->getId(),
            $component->getName(),
            $component->getEan13(),
            $component->getQuantity(),
            $component->getQuantityThreshold(),
        );
    }

Well the questions are :

  • Why does PATCH and PUT works for real but not in unit tests ? How to make it work ?
  • Why is the Provider called even if ONLY the Processor is specified in Api Platform attributes ?

Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa Dịch vụ tổ chức sự kiện 5 sao Thông tin về chúng tôi Dịch vụ sinh nhật bé trai Dịch vụ sinh nhật bé gái Sự kiện trọn gói Các tiết mục giải trí Dịch vụ bổ trợ Tiệc cưới sang trọng Dịch vụ khai trương Tư vấn tổ chức sự kiện Hình ảnh sự kiện Cập nhật tin tức Liên hệ ngay Thuê chú hề chuyên nghiệp Tiệc tất niên cho công ty Trang trí tiệc cuối năm Tiệc tất niên độc đáo Sinh nhật bé Hải Đăng Sinh nhật đáng yêu bé Khánh Vân Sinh nhật sang trọng Bích Ngân Tiệc sinh nhật bé Thanh Trang Dịch vụ ông già Noel Xiếc thú vui nhộn Biểu diễn xiếc quay đĩa Dịch vụ tổ chức tiệc uy tín Khám phá dịch vụ của chúng tôi Tiệc sinh nhật cho bé trai Trang trí tiệc cho bé gái Gói sự kiện chuyên nghiệp Chương trình giải trí hấp dẫn Dịch vụ hỗ trợ sự kiện Trang trí tiệc cưới đẹp Khởi đầu thành công với khai trương Chuyên gia tư vấn sự kiện Xem ảnh các sự kiện đẹp Tin mới về sự kiện Kết nối với đội ngũ chuyên gia Chú hề vui nhộn cho tiệc sinh nhật Ý tưởng tiệc cuối năm Tất niên độc đáo Trang trí tiệc hiện đại Tổ chức sinh nhật cho Hải Đăng Sinh nhật độc quyền Khánh Vân Phong cách tiệc Bích Ngân Trang trí tiệc bé Thanh Trang Thuê dịch vụ ông già Noel chuyên nghiệp Xem xiếc khỉ đặc sắc Xiếc quay đĩa thú vị
Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa
Thiết kế website Thiết kế website Thiết kế website Cách kháng tài khoản quảng cáo Mua bán Fanpage Facebook Dịch vụ SEO Tổ chức sinh nhật