Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laravel Mojito Laravel Package

nunomaduro/laravel-mojito

Laravel Mojito is a lightweight testing helper by Nuno Maduro that simplifies mocking Laravel facades. Define expectations and return values with a clean, fluent API, keeping tests fast and readable without boilerplate.

View on GitHub
Deep Wiki
Context7

Getting Started

Install (dev-only):

composer require --dev nunomaduro/laravel-mojito

Because the last release is from April 2021, check the composer.json constraints before committing to it. It was built for PHP 7.x and Laravel 5.5–8; on Laravel 9+ you will likely need to fork it or skip it (see Gotchas). v0.2.8 adds an empty assertion but is otherwise a patch-level release — no breaking changes, no new requirements, so an existing pin to ^0.2 picks it up for free.

Where to look first:

  1. README.md for the assertion API surface — it is a very small package, so the docs are the best map. As of v0.2.8 the surface is contains(), doesNotContain(), and empty().
  2. src/Mojito.php and the assertion/test-case classes — the whole package is a few hundred lines, and reading it is faster than guessing at behaviour.
  3. The package's own tests/ directory — these are the most reliable, up-to-date examples of supported usage. Check whether the empty assertion landed with a test; if it did not, that tells you something about the release's maturity.

First use case — assert a Blade view renders in isolation:

The core idea is that you should not need a full HTTP feature test to know that resources/views/welcome.blade.php renders the name you passed it. Instead:

use NunoMaduro\Mojito\Tests\TestCase;

class WelcomeViewTest extends TestCase
{
    public function test_it_renders_the_name(): void
    {
        $this->assertViewRenders('welcome', ['name' => 'Nuno Maduro'])
            ->contains('Nuno Maduro');
    }
}

Start by migrating one or two "view-shaped" assertions out of a slow Feature test into a Mojito test. That gives you an immediate feel for the boot cost and the fidelity trade-off before you convert a whole suite.

Implementation Patterns

1. One test per view, data-driven with fixtures.

The sweet spot is a table of data arrays per view, asserted in a loop:

/**
 * @dataProvider welcomeProvider
 */
public function test_welcome_renders(array $data, string $expected): void
{
    $this->assertViewRenders('welcome', $data)->contains($expected);
}

public function welcomeProvider(): array
{
    return [
        'named user'   => [['name' => 'Nuno'], 'Nuno'],
        'empty name'   => [['name' => ''], 'Welcome'],
        'admin flag'   => [['name' => 'A', 'admin' => true], 'Admin'],
    ];
}

This is where Mojito earns its keep: the failure message points at a view + data combination, not at a route, middleware stack, or session state.

2. Separate "does the view render" from "does the endpoint work".

A useful split in a mature suite:

  • Mojito tests — Blade markup, conditional blocks, loops, component output, escaping behaviour.
  • Feature tests — routing, middleware, auth, validation, redirects, DB side effects.

Feature tests then assert one happy-path render and stop duplicating every @if branch. This is the highest-leverage change you can make with this package.

3. Assertion chains for presence, absence, and emptiness.

Pair contains() with its negative counterpart to lock down conditional rendering:

$this->assertViewRenders('dashboard', ['admin' => false])
    ->doesNotContain('Admin panel');

$this->assertViewRenders('dashboard', ['admin' => true])
    ->contains('Admin panel');

Treat doesNotContain as the more valuable assertion — it catches accidentally leaked markup far more often than positive assertions catch missing markup.

4. Use empty() for views that are supposed to render nothing.

The v0.2.8 addition is the answer to a real annoyance: proving a negative. Conditional partials, x-slot-driven wrappers, mail fragments that only appear for some users, and JSON-LD/analytics snippets gated behind config all have a "renders nothing at all" state that is awkward to express with doesNotContain (you end up guessing at strings that might not exist anyway).

$this->assertViewRenders('partials.admin-badge', ['admin' => false])->empty();

$this->assertViewRenders('partials.admin-badge', ['admin' => true])
    ->contains('Admin');

Use it as the negative half of a pair, the same way you use doesNotContain. It is a stronger claim than "this string is missing" — it asserts the whole render produced no output — so it belongs on small, single-purpose templates rather than on full pages, which almost always emit surrounding whitespace or layout markup. Verify against your installed version before relying on it in a shared suite: the method is new in 0.2.8, and older pins in a lockfile or a CI image will fail with a BadMethodCallException.

5. Use it for Blade component and partial work.

When you are iterating on a single component (x-alert, a mail partial, a PDF template), a Mojito test gives you a sub-second feedback loop without touching a controller. This is the closest thing to "unit testing a view" that Laravel offers out of the box.

6. Combine with snapshot testing for whole-output regression.

Mojito tells you a string is present; it does not tell you the layout did not shift. For views you care about visually, pair it with a snapshot/spatie-style assertion on the full rendered HTML, and keep Mojito for the semantic checks. Snapshots also cover the empty() case from the other direction — a snapshot of '' is unmissable in review.

7. Run it as its own CI lane.

Because each test renders a single view rather than booting a request, these run fast. Group them under a separate PHPUnit testsuite (e.g. tests/Views) so a broken view fails fast in CI before the feature suite runs.

Gotchas and Tips

Maintenance is the headline risk. Last release 2021-04-08 (v0.2.8), zero dependents, low overall score. v0.2.8 is a single-assertion patch, not a sign of revival — assume the compatibility floor has not moved. Before adopting, run the suite against your Laravel version. Common failure modes on Laravel 9+:

  • Facade/container bootstrapping changes break the minimal test case.
  • Blade compiler API changes (BladeCompiler::compileString, component resolution) break rendering.
  • PHP 8.1+ deprecations from 7.x-era code.

If you hit these, the package is small enough to copy the ~200 lines into tests/Support/ and own it. That is often cheaper than a fork with a Composer vcs repository entry. If you do vendor it, port empty() too — it is a handful of lines and removes the most common reason to reach for a brittle doesNotContain guess.

empty() is whitespace-sensitive. It asserts the render produced nothing, so a template ending in a newline after @endif, or a leading blank line from a @php block, will fail it. Trim the template or assert on a narrower partial. When it fails confusingly, dump the render (see below) — the culprit is usually invisible whitespace, not unexpected markup.

Reduced application fidelity is by design. Mojito boots a lightweight container, so anything resolved at render time may behave differently than in a real request:

  • auth()->user(), Gate::allows(), and policy checks — inject stubs or use actingAs if supported.
  • View composers and View::share() from service providers may not be registered. If a view depends on globally shared data, pass it explicitly in the data array instead of relying on the composer.
  • config(), route(), url(), and localization helpers can be partially wired; a missing route name will throw at render time, not at assert time.

This matters doubly for empty(): a view whose only content comes from a view composer will render empty under Mojito and non-empty in production. A passing empty() test can therefore be a false positive if the content source is shared data rather than template logic. Prefer empty() on partials that branch on passed-in data.

Blade components and slots need explicit data. <x-*> components resolve their own class and may require constructor dependencies; if a component injects a service, the minimal container can fail to resolve it. Test the component's Blade file directly with plain data, or fall back to a normal feature test for component-heavy views.

Debugging a failing assertion is not obvious. The assertion output often shows only "string not found" without the rendered HTML. Get in the habit of dumping the render first:

$html = view('welcome', ['name' => 'Nuno'])->render();
dump($html); // then write the Mojito assertion against what you actually see

If view()->render() and the Mojito render disagree, that difference is the fidelity gap — usually a missing composer or shared variable. For empty(), dump(strlen($html)) is quicker than eyeballing whitespace.

Escaping will bite you. contains() matches raw output, so Nuno & Sons renders as Nuno &amp; Sons. Assert against the escaped form, or you will chase phantom failures.

Whitespace and formatting are literal. Multi-line Blade output contains the indentation from the template. Prefer short, distinctive strings ('Admin panel') over full sentences that a formatter or @php block can reflow.

Extension points are minimal — that is the point. There is no plugin system. If you need a custom assertion, subclass the test case or write a trait wrapping assertViewRenders. Do not expect to hook into the renderer.

Have a no-dependency fallback ready. If compatibility becomes a burden, the 90% substitute is:

$this->assertStringContainsString('Nuno', view('welcome', $data)->render());
$this->assertSame('', view('partials.admin-badge', $data)->render());

It lacks the ergonomics and the isolation framing, but it has zero maintenance risk and works on every Laravel version. Evaluate Mojito on whether the assertion API and failure ergonomics are worth the pinning cost for your team — for a 367-star, four-year-stale package, that bar is high, and one new assertion does not change it.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
ignibyte/aic-framework
glavweb/data-schema-bundle
glavweb/datagrid-bundle
tacticmedia/scim-server
uni-method/frame
neika-ai/sdk
adachsoft/php-code-style
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
adachsoft/api-version-bundle
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox