3

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"
Alex 75
  • 2,798
  • 1
  • 31
  • 48
  • Not the question, but out of curiosity why do you need to mock the logger? If you just need it for injection you can instantiate a NullLogger, if you want to check what gets logged there are implemenations that will help you with that. I've also remembered the answer... – Murph May 31 '22 at 14:05
  • Current behaviour of the application is to log something for success and something else for error (of a step in a bigger command). ILogger is what actually came out of the box from Azure Functions. https://learn.microsoft.com/en-us/azure/azure-functions/functions-dotnet-dependency-injection . I have the task to improve the tests, cannot decide to change Logger. I don't think NullLogger allows me to check its behaviour... so I need a mock. – Alex 75 May 31 '22 at 15:58
  • There are dedicated mock implementations of Ilogger - with message capture. But I haven't worked with these with F# only with C# and xUnit. How to make it work with Foq is an interesting question... – Murph Jun 01 '22 at 14:45

1 Answers1

2

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.

Murph
  • 9,985
  • 2
  • 26
  • 41
  • Thanks. I haven't realized they were extension methods. I'm trying to mock ``.Log(logLevel, message)`` but atill not able to have it working. – Alex 75 May 31 '22 at 16:06