Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using Foq to mock Microsoft.Extensions.Logging.ILogger

Tags:

f#

foq

I'm having difficulties to Mock Microsoft ILogger with Foq.

module MockExample

open Microsoft.Extensions.Logging
open NUnit.Framework
open Foq
//open Foq.Linq


// Implementation

type Worker (logger:ILogger) =

    member this.DoSomething (ok:bool) =
        if ok then
            logger.LogInformation("success 1")
        else
            logger.LogError("error 1")


// Test file

[<Test>]
let ``When error should log error`` () =
    
    // 1. let logger = Mock<ILogger>().SetupAction(fun lg -> lg.LogError("error")) ...Create() not availabe

    // 2.
    //let loggerMock = Mock<ILogger>(); 
    //loggerMock.SetupFunc(fun lg -> lg.LogError(It.IsAny<string>())) |> ignore  // .Returns() is not usable
    //let logger = loggerMock.Create()

    let logger = mock()

    // act
    Worker(logger).DoSomething(false)

    verify <@ logger.LogError("error 1") @> once
    //verify <@ logger.LogError("success 1") @> never

Test does not pass.
I tried also other syntax to try to "mimic" the LogError signature but withut cuccess.

[EDIT]

As sugegsted I'm trying to mock the real logger method .Log(logLevel, message, ...), but still not able to get it working:

mock Log(...)

Essentially I don't know how to mock this:

member Log<'TState> : logLevel: LogLevel * eventId: EventId * state: 'TState * ``exception`` : exn * formatter: System.Func<'TState,exn,string> -> unit

to extract/register/compare the state (it contains the log "message").

[Edit 2]

As a workaround, I wrote my mock of the ILogger and it serve the purpose, but my question is still "how to do it with Foq" ?

type LoggerMock () =
    let mutable informations:string list = []
    let mutable errors:string list = []

    member this.LoggedInformations = informations
    member this.LoggedErrors = errors    

    interface ILogger with
        member this.Log(logLevel: LogLevel, eventId: EventId, state: 'TState, ``exception``: exn, formatter: System.Func<'TState,exn,string>): unit = 
            match logLevel with
            | LogLevel.Information -> informations <- informations @ [state.ToString()]
            | LogLevel.Error -> errors <- errors @ [state.ToString()]
            | _ -> ()
        member this.BeginScope(state: 'TState): System.IDisposable = 
            raise (NotImplementedException())
        member this.IsEnabled(logLevel: LogLevel): bool = 
            raise (NotImplementedException())
                

[<Test>]
let ``When error should log error (custom mock)`` () =    
    let logger = LoggerMock()
    Worker(logger).DoSomething(false)
    logger.LoggedErrors |> should contain "error 2"
like image 598
Alex 75 Avatar asked Sep 18 '25 08:09

Alex 75


1 Answers

The problem is that .LogError and .LogInformation are not actually methods on ILogger, but extension methods that wrap the log method that ILogger actually defines.

The method you need to mock is: ILogger.Log Method

I believe the extension methods all call that one method.

like image 116
Murph Avatar answered Sep 20 '25 01:09

Murph