• Stars
    star
    380
  • Rank 112,766 (Top 3 %)
  • Language
    PHP
  • License
    MIT License
  • Created about 6 years ago
  • Updated over 1 year ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

A drop in fake logger for testing with the Laravel framework.

Log Fake: a Laravel package by Tim MacDonald

Log fake for Laravel

CI codecov Mutation testing badge Total Downloads

A bunch of Laravel facades / services are able to be faked, such as the Dispatcher with Bus::fake(), to help with testing and assertions. This package gives you the ability to fake the logger in your app, and includes the ability to make assertions against channels, stacks, and a whole bunch more introduced in the logging overhaul from Laravel 5.6.

Version support

  • PHP: 8.1, 8.2
  • Laravel: 9.0, 10.0
  • PHPUnit: 9.0, 10.0

You can find support for older versions in previous releases.

Installation

You can install using composer from Packagist.

composer require timacdonald/log-fake --dev

Basic usage

public function testItLogsWhenAUserAuthenticates()
{
    /*
     * Test setup.
     *
     * In the setup of your tests, you can call the following `bind` helper,
     * which will switch out the underlying log driver with the fake.
     */
    LogFake::bind();

    /*
     * Application implementation.
     *
     * In your application's implementation, you then utilise the logger, as you
     * normally would.
     */
    Log::info('User logged in.', ['user_id' => $user->id]);

    /*
     * Test assertions.
     *
     * Finally you can make assertions against the log channels, stacks, etc. to
     * ensure the expected logging occurred in your implementation.
     */
    Log::assertLogged(fn (LogEntry $log) =>
        $log->level === 'info'
        && $log->message === 'User logged in.' 
        && $log->context === ['user_id' => 5]
    );
}

Channels

If you are logging to a specific channel (i.e. not the default channel) in your app, you need to also prefix your assertions in the same manner.

public function testItLogsWhenAUserAuthenticates()
{
    // setup...
    LogFake::bind();

    // implementation...
    Log::channel('slack')->info('User logged in.', ['user_id' => $user->id]);

    // assertions...
    Log::channel('slack')->assertLogged(
        fn (LogEntry $log) => $log->message === 'User logged in.'
    );
}

Stacks

If you are logging to a stack in your app, like with channels, you will need to prefix your assertions. Note that the order of the stack does not matter.

public function testItLogsWhenAUserAuthenticates()
{
    // setup...
    LogFake::bind();

    // implementation...
    Log::stack(['stderr', 'single'])->info('User logged in.', ['user_id' => $user->id]);

    // assertions...
    Log::stack(['stderr', 'single'])->assertLogged(
        fn (LogEntry $log) => $log->message === 'User logged in.'
    );
}

That's it really. Now let's dig into the available assertions to improve your experience testing your applications logging.

Available assertions

Remember that all assertions are relative to the channel or stack as shown above.

assertLogged()

Assert that a log was created.

Can be called on

  • Facade base (default channel)
  • Channels
  • Stacks

Example tests

/*
 * implementation...
 */

Log::info('User logged in.');

/*
 * assertions...
 */

Log::assertLogged(
    fn (LogEntry $log) => $log->message === 'User logged in.'
); // βœ…

Log::assertLogged(
    fn (LogEntry $log) => $log->level === 'critical'
); // ❌ as log had a level of `info`.

assertLoggedTimes()

Assert that a log was created a specific number of times.

Can be called on

  • Facade base (default channel)
  • Channels
  • Stacks

Example tests

/*
 * implementation...
 */

Log::info('Stripe request initiated.');

Log::info('Stripe request initiated.');

/*
 * assertions...
 */

Log::assertLoggedTimes(
    fn (LogEntry $log) => $log->message === 'Stripe request initiated.',
    2
); // βœ…

Log::assertLoggedTimes(
    fn (LogEntry $log) => $log->message === 'Stripe request initiated.',
    99
); // ❌ as the log was created twice, not 99 times.

assertNotLogged()

Assert that a log was never created.

Can be called on

  • Facade base (default channel)
  • Channels
  • Stacks

Example tests

/*
 * implementation...
 */

Log::info('User logged in.');

/*
 * assertions...
 */

Log::assertNotLogged(
    fn (LogEntry $log) => $log->level === 'critical'
); // βœ…

Log::assertNotLogged(
    fn (LogEntry $log) => $log->level === 'info'
); // ❌ as the level was `info`.

assertNothingLogged()

Assert that no logs were created.

Can be called on

  • Facade base (default channel)
  • Channels
  • Stacks

Example tests

/*
 * implementation...
 */

Log::channel('single')->info('User logged in.');

/*
 * assertions...
 */

Log::channel('stderr')->assertNothingLogged(); // βœ…

Log::channel('single')->assertNothingLogged(); // ❌ as a log was created in the `single` channel.

assertWasForgotten()

Assert that the channel was forgotten at least one time.

Can be called on

  • Facade base (default channel)
  • Channels
  • Stacks

Example tests

/*
 * implementation...
 */

Log::channel('single')->info('User logged in.');

Log::forgetChannel('single');

/*
 * assertions...
 */

Log::channel('single')->assertWasForgotten(); // βœ…

Log::channel('stderr')->assertWasForgotten(); // ❌ as it was the `single` not the `stderr` channel that was not forgotten.

assertWasForgottenTimes()

Assert that the channel was forgotten a specific number of times.

Can be called on

  • Facade base (default channel)
  • Channels
  • Stacks

Example tests

/*
 * implementation...
 */

Log::channel('single')->info('User logged in.');

Log::forgetChannel('single');

Log::channel('single')->info('User logged in.');

Log::forgetChannel('single');

/*
 * assertions...
 */

Log::channel('single')->assertWasForgottenTimes(2); // βœ…

Log::channel('single')->assertWasForgottenTimes(99); // ❌ as the channel was forgotten twice, not 99 times.

assertWasNotForgotten()

Assert that the channel was not forgotten.

Can be called on

  • Facade base (default channel)
  • Channels
  • Stacks

Example tests

/*
 * implementation...
 */

Log::channel('single')->info('User logged in.');

/*
 * assertions...
 */

Log::channel('single')->assertWasNotForgotten(); // βœ…

assertChannelIsCurrentlyForgotten()

Assert that a channel is currently forgotten. This is distinct from asserting that a channel was forgotten.

Can be called on

  • Facade base (default channel)
  • Channels
  • Stacks

Example tests

/*
 * implementation...
 */

Log::channel('single')->info('xxxx');

Log::forgetChannel('single');

/*
 * assertions...
 */

Log::assertChannelIsCurrentlyForgotten('single'); // βœ…

Log::assertChannelIsCurrentlyForgotten('stderr'); // ❌ as the `single` channel was forgotten, not the `stderr` channel.

assertCurrentContext()

Assert that the channel currently has the specified context. It is possible to provide the expected context as an array or alternatively you can provide a truth-test closure to check the current context.

Can be called on

  • Facade base (default channel)
  • Channels
  • Stacks

Example tests

/*
 * implementation...
 */

Log::withContext([
    'app' => 'Acme CRM',
]);

Log::withContext([
    'env' => 'production',
]);

/*
 * assertions...
 */

Log::assertCurrentContext([
    'app' => 'Acme CRM',
    'env' => 'production',
]); // βœ…

Log::assertCurrentContext(
    fn (array $context) => $context['app'] === 'Acme CRM')
); // βœ…

Log::assertCurrentContext([
    'env' => 'production',
]); // ❌ missing the "app" key.

Log::assertCurrentContext(
    fn (array $context) => $context['env'] === 'develop')
); // ❌ the 'env' key is set to "production"

Inspection

Sometimes when debugging tests it's useful to be able to take a peek at the messages that have been logged. There are a couple of helpers to assist with this.

dump()

Dumps all the logs in the channel. You can also pass a truth-based closure to filter the logs that are dumped.

Can be called on

  • Facade base (default channel)
  • Channels
  • Stacks
/*
 * implementation...
 */

Log::info('User logged in.');

Log::channel('slack')->alert('Stripe request initiated.');

/*
 * inspection...
 */

Log::dump();

// array:1 [
//   0 => array:4 [
//     "level" => "info"
//     "message" => "User logged in."
//     "context" => []
//     "channel" => "stack"
//   ]
// ]

Log::channel('slack')->dump();

// array:1 [
//   0 => array:4 [
//     "level" => "alert"
//     "message" => "Stripe request initiated."
//     "context" => []
//     "channel" => "slack"
//   ]
// ]

dd()

The same as dump, but also ends the execution.

dumpAll()

Dumps the logs for all channels. Also accepts a truth-test closure to filter any logs.

Can be called on

  • Facade base (default channel)
  • Channels
  • Stacks

Example usage

/*
 * implementation...
 */

Log::info('User logged in.');

Log::channel('slack')->alert('Stripe request initiated.');

/*
 * inspection...
 */

Log::dumpAll();

// array:2 [
//   0 => array:4 [
//     "level" => "info"
//     "message" => "User logged in."
//     "context" => []
//     "times_channel_has_been_forgotten_at_time_of_writing_log" => 0
//     "channel" => "stack"
//   ]
//   1 => array:4 [
//     "level" => "alert"
//     "message" => "Stripe request initiated."
//     "context" => []
//     "times_channel_has_been_forgotten_at_time_of_writing_log" => 0
//     "channel" => "slack"
//   ]
// ]

ddAll()

The same as dumpAll(), but also ends the execution.

Other APIs

logs()

Get a collection of all log entries from a channel or stack.

Can be called on

  • Facade base (default channel)
  • Channels
  • Stacks

Example usage

/*
 * implementation...
 */

Log::channel('slack')->info('User logged in.');

Log::channel('slack')->alert('Stripe request initiated.');

/*
 * example usage...
 */

$logs = Log::channel('slack')->logs();

assert($logs->count() === 2); βœ…

allLogs()

Similar to logs(), except that it is called on the Facade base and returns a collection of logs from all the channels and stacks.

Can be called on

  • Facade base (default channel)
  • Channels
  • Stacks

Example usage

/*
 * implementation...
 */

Log::info('User logged in.');

Log::channel('slack')->alert('Stripe request initiated.');

/*
 * example usage...
 */

$logs = Log::allLogs();

assert($logs->count() === 2); βœ…

Credits

And a special (vegi) thanks to Caneco for the logo ✨

Thanksware

You are free to use this package, but I ask that you reach out to someone (not me) who has previously, or is currently, maintaining or contributing to an open source library you are using in your project and thank them for their work. Consider your entire tech stack: packages, frameworks, languages, databases, operating systems, frontend, backend, etc.

More Repositories

1

json-api

A lightweight API resource for Laravel that helps you adhere to the JSON:API standard. Supports sparse fieldsets, compound documents, and more.
PHP
382
star
2

has-parameters

A trait that allows you to pass arguments to Laravel middleware in a more PHP'ish way.
PHP
207
star
3

rule-builder

Fluent validation rule builder for Laravel.
PHP
100
star
4

multiformat-response-objects

Response objects for handling multiple response formats within the one controller
PHP
65
star
5

callable-fake

A PHP testing utility that allows you to fake, capture, and assert against invocations of a callable / Closure
PHP
41
star
6

debounced-notifications

Basecamp style notification debouncing / throttling for Laravel notifications.
PHP
30
star
7

flipit

Zero Downtime Deployment Script
Shell
28
star
8

dotfiles

My dotfiles. Mine, not yours. But you're welcome to borrow them anytime.
Vim Script
13
star
9

cached-valuestore

An extension of spatie/valuestore with in-memory caching.
PHP
10
star
10

pulse-validation-errors

Validation errors card for Laravel Pulse
PHP
9
star
11

immutable-carbon

An immutable version of the PHP Carbon date library.
PHP
6
star
12

multisite-backup-command

A wrapper around spatie/laravel-backup backup command to make it super simple to backup multiple sites on a single server.
PHP
5
star
13

php-style

PHP-CS-Fixer rules for packages
PHP
4
star
14

fresco

A fresh take on the PHP documentation website generation.
PHP
4
star
15

laravel-container-speed-test

Speed testing the Laravel container instantiation during tests
PHP
2
star
16

kumulos-api

Kumulos PHP API SDK
PHP
2
star
17

php-style-example

Share your PHP-CS-Fixer config across multiple projects.
PHP
2
star
18

json-api-talk

PHP
1
star
19

laravel-access-validated-inputs

A trait to ensure valid data access in your form request object
PHP
1
star