[]]); }); it('uses CastWith attribute over global config caster', function () { $globalCaster = function (mixed $value): SimpleValue { return new SimpleValue($value['value'] * 3); }; config(['dto.cast.' . SimpleValue::class => $globalCaster]); $object = WithSpecificCaster::fromArray([ 'value' => ['value' => 5], ]); expect($object->value->value)->toBe(10); // 5 * 2 }); it('falls back to global config caster when no CastWith attribute', function () { $globalCaster = function (mixed $value): SimpleValue { return new SimpleValue($value['value'] * 3); }; config(['dto.cast.' . SimpleValue::class => $globalCaster]); $object = WithGlobalCaster::fromArray([ 'value' => ['value' => 5], ]); expect($object->value->value)->toBe(15); // 5 * 3 }); it('falls back to default construction when no caster exists', function () { $object = WithoutCaster::fromArray([ 'value' => ['value' => 5], ]); expect($object)->toBeInstanceOf(WithoutCaster::class); }); }); describe('caster with rules', function () { beforeEach(function () { config(['dto.cast' => []]); }); it('validates input using caster rules before casting', function () { expect(fn() => WithSpecificCaster::fromArray([ 'value' => [], ]))->toThrow(ValidationException::class); }); it('accepts valid input and casts correctly', function () { $object = WithSpecificCaster::fromArray([ 'value' => ['value' => 10], ]); expect($object->value->value)->toBe(20); // 10 * 2 }); });