Testing Helpers

Drop the InteractsWithTrees trait into your PHPUnit test classes to shorten the boilerplate around assertions on tree state:

use Vusys\NestedSet\Testing\InteractsWithTrees;

final class CategoryTreeTest extends TestCase
{
    use InteractsWithTrees;

    public function test_appending_child_keeps_tree_intact(): void
    {
        $root = Category::factory()->create();
        $child = new Category(['name' => 'child']);
        $child->appendToNode($root)->save();

        $this->assertIsRoot($root->refresh());
        $this->assertIsChildOf($child, $root);
        $this->assertIsLeaf($child);
        $this->assertHasChildren($root, 1);
        $this->assertHasDescendants($root, 1);
        $this->assertTreeIsIntact(Category::class);
    }
}

Available assertions

Assertion What it checks
assertIsRoot($node) parent_id is NULL
assertIsLeaf($node) / assertIsNotLeaf($node) rgt = lft + 1 (no descendants)
assertIsChildOf($node, $parent) direct parent: parent_id matches, depth = parent.depth + 1
assertIsDescendantOf($node, $ancestor) strict containment via NodeBounds::contains()
assertIsAncestorOf($a, $b) symmetric counterpart of the above
assertHasDescendants($node, $count) exact descendant count, derived from (rgt - lft - 1) / 2 (no extra query)
assertHasChildren($node, $count) exact direct-child count (one query)
assertAggregateMatchesFresh($node, $column) stored aggregate equals freshly-computed value, with numeric tolerance
assertTreeIsIntact($modelClass, ?$anchor) wraps isBroken(); failure message includes countErrors() breakdown
assertAggregatesAreIntact($modelClass, ?$anchor) wraps aggregatesAreBroken(); failure message includes per-column drift; fails fast with a clear message when the model declares no aggregates

The trait depends only on the HasNestedSet contract for parameters that don't need DB access, and on Model & HasNestedSet for the few that do (assertHasChildren, assertAggregateMatchesFresh).