Giter Site home page Giter Site logo

log-fake's Introduction

Log Fake: a Laravel package by Tim MacDonald

Log fake for Laravel

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.

Installation

You can install using composer:

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"

assertHasSharedContext()

Assert that the Log manager currently has the given shared 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::shareContext([
    'invocation-id' => '54',
]);

/*
 * assertions...
 */

Log::assertHasSharedContext([
    'invocation-id' => '54',
]); // ✅

Log::assertCurrentContext(
    fn (array $context) => $context['invocation-id'] === '54')
); // ✅

Log::assertCurrentContext([
    'invocation-id' => '99',
]); // ❌ wrong invocation ID

Log::assertCurrentContext(
    fn (array $context) => $context['invocation-id'] === '99')
); // ❌ wrong invocation ID

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.

log-fake's People

Contributors

alexvanderbist avatar brentkelly avatar caneco avatar dependabot-preview[bot] avatar it-can avatar laravel-shift avatar lednerb avatar ludo237 avatar rmachado-studocu avatar romkatsu avatar sebastiaanluca avatar simoebenhida avatar timacdonald avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

log-fake's Issues

PHPUnit 10

Move phpunit to require-dev or add support for version 10 please!

listen method support ?

Is there a way to keep Log::listen functionality after using LogFake::bind():

I do need such method to keep track on all unexpected logs during tests:

    /**
     * Fails the tests when an unexpected error is logged.
     */
    protected function setUpLogListener(): void
    {
        Log::listen(function (MessageLogged $msg) {
            if ($msg->level === 'error') {
                $this->assertTrue($this->expectErrorLog, "An unexpected error is logged: {$msg->message}");
            }
        });
    }

Log assertion not hit after expecting an exception in PHPUnit

I'm on Lumen 8.0, with PHPUnit 9.3.

I'm testing a piece of code which throws a custom exception, then logs to a channel inside the report() method of my exception.

Here's my test:

$this->expectException(FileDecompressorException::class);

[code which throws an exception]

Log::channel('cloudwatch')->assertLogged('error');

Here's my Exception:

class FileDecompressorException extends Exception
{
    const MESSAGE = "Encountered the following exception extracting Issue ZIP: %s: %s";

    public function __construct($message = "", $code = 0, Exception $previous = null)
    {
        $this->message = $message;

        $this->report();
        parent::__construct($this->message, $code, $previous);
    }

    public function report()
    {
        Log::channel('cloudwatch')
            ->error(
                $this->message
            );
    }
}

PHP output:

OK (1 test, 1 assertion)

But there are two assertions in this test. In fact if I change the assertion to check for a different message the test passes. I've placed a dd() inside my report() method to verify that the Log code is being hit.

Is this an issue with the package? Or something my side?

Laravel 10 Support

Hey!

First of thanks for the great package. Helped my team test out so many different logs! Great work!

Do you plan the support for Laravel 10 with this package?

LogFake::bind() is not working with Laravel 8.x

After LogFake is installed via composer, the tests complains about the method bind() not exists. So we can't use this package as described in the docs.

image

But even without the bind() the log isn't written on the log file (It was written before). Maybe this is just an old documentation?

Expected log was not created in the [channel_name] channel

Hi. I am using Laravel 10.25.1.
And when i want test logging using your package, i get an error: Expected log was not created in the [stderr_json] channel. Failed asserting that false is true.

Here's the code:

Log::channel(channel: 'stderr_json')->assertLogged(callback: function (LogEntry $log) {
          //dd($log);
          //$this->assertEquals(expected: 'stderr_json', actual: $log->channel);
      }
  );

But, when i am call dd(), i get the following:

TiMacDonald\Log\LogEntry^ {#1986 // tests/Feature/Clients/Exceptions/DomainNameCreationExceptionTest.php:98
  +level: "error"
  +message: """
     | Stack trace: #0 /src/vendor/phpunit/phpunit/src/Framework/TestCase.php(1102): Feature\Clients\Exceptions\DomainNameCreationExceptionTest->test_correctness_content_exception()\n
    #1 /src/vendor/laravel/framework/src/Illuminate/Foundation/Testing/TestCase.php(174): PHPUnit\Framework\TestCase->runTest()\n
    #2 /src/vendor/phpunit/phpunit/src/Framework/TestCase.php(653): Illuminate\Foundation\Testing\TestCase->runTest()\n
    #3 /src/vendor/phpunit/phpunit/src/Framework/TestRunner.php(103): PHPUnit\Framework\TestCase->runBare()\n
    #4 /src/vendor/phpunit/phpunit/src/Framework/TestCase.php(489): PHPUnit\Framework\TestRunner->run(Object(Feature\Clients\Exceptions\DomainNameCreationExceptionTest))\n
    #5 /src/vendor/phpunit/phpunit/src/Framework/TestSuite.php(340): PHPUnit\Framework\TestCase->run()\n
    #6 /src/vendor/phpunit/phpunit/src/Framework/TestSuite.php(340): PHPUnit\Framework\TestSuite->run()\n
    #7 /src/vendor/phpunit/phpunit/src/Framework/TestSuite.php(340): PHPUnit\Framework\TestSuite->run()\n
    #8 /src/vendor/phpunit/phpunit/src/TextUI/TestRunner.php(63): PHPUnit\Framework\TestSuite->run()\n
    #9 /src/vendor/phpunit/phpunit/src/TextUI/Application.php(176): PHPUnit\TextUI\TestRunner->run(Object(PHPUnit\TextUI\Configuration\Configuration), Object(PHPUnit\Runner\ResultCache\DefaultResultCache), Object(PHPUnit\Framework\TestSuite))\n
    #10 /src/vendor/phpunit/phpunit/phpunit(99): PHPUnit\TextUI\Application->run(Array)\n
    #11 {main}
    """
  +context: []
  +timesChannelHasBeenForgottenAtTimeOfWritingLog: 0
  +channel: "stderr_json"
}

What am I doing wrong, what could be the problem?

[Request] Support for Log::shareContext

We use Log::shareContext, but it result in

Error : Call to undefined method TiMacDonald\Log\ChannelFake::shareContext()

Our use case is exactly the same as the example in the Laravel manual, and we use it to assign a unique id to each invocation.

Contextual Information

Support for Log::withContext($context)

We use Log::withContext($context)

This method was introduced in Laravel 8.49 and adds cascading context to all following log calls.
Unfortunately we need to wrap this in a try/catch in tests, otherwise it results in:

Error: Call to undefined method TiMacDonald\Log\LogFake::withContext()

This makes it impossible to test the context in logs that are dependent on this method call

Check 2 messages in the same channel

Hello.

Is it possible to check 2 times messages in the channel?

For example:

Log::info('foo');
Log::info('bar');

//...

Log::assertLogged('info', function ($message, $context) {
    return str_contains($message, 'foo');
});
//this one gives an error
Log::assertLogged('info', function ($message, $context) {
    return str_contains($message, 'bar');
});

useless when called multiple times

    function test_asd()
    {
        Log::swap(new LogFake);

        $myFun = function ($i) {
            $a = random_int(0, 1000);
            var_dump('a', $a);
            Log::channel('just_for_testing')->debug($a, ['i' => $i]);
        };

        foreach ([1, 2, 3] as $i) {
            $myFun($i);

            Log::channel('just_for_testing')->assertLogged('debug',
                function ($message, $context) use ($i) {
                    var_dump($message, $context);

                    self::assertEquals($i, $context['i']);

                    return true;
                });
        }
    }
string(1) "a"
int(86)
int(86)
array(1) {
  ["i"]=>
  int(1)
}
string(1) "a"
int(368)
int(86)
array(1) {
  ["i"]=>
  int(1)
}

Failed asserting that 1 matches expected 2.
Expected :2
Actual   :1
Failed asserting that 1 matches expected 2.
Expected :2
Actual   :1

I just wasted an hour, thanks

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.