Greg B
Greg B

Reputation: 14898

How do I write to the Azure Devops task log from xUnit tests?

I'm running some tests in an Azure Devops pipeline and I'm seeing some failures on the build agent that I don't get locally. I'm trying some low-fi debugging and want to write some chatter out to task log but I can't see how. I've tried Console.WriteLine(), Debug.WriteLine() and Trace.WriteLine() but I don't see any of my messages in the log.

How can I write to the pipeline task log?

Upvotes: 14

Views: 3795

Answers (3)

SymboLinker
SymboLinker

Reputation: 1199

In case of Google Cloud Build Log, Console.WriteLine does work (current year: 2022, using: xunit 2.4.1):

Step #0: Starting test execution, please wait...
Step #0: A total of 1 test files matched the specified pattern.
Step #0: Hello world!

Below an example of a TestLogger that logs to

  • Google Cloud Build Log,
  • Test Explorer Standard Output,
  • Property TestLogger.Messages for assert statements.

I made the ITestOutputHelper parameter optional in the TestLogger constructor, because when using the TestLogger.Messages property you may not want to also log to other outputs (but it might be nice to have only one TestLogger in you unit test projects).

using Microsoft.Extensions.Logging;
using Xunit.Abstractions;

public class TestLogger<T> : TestLogger, ILogger<T>
{
    public TestLogger(ITestOutputHelper? testOutputHelper = null) : base(testOutputHelper)
    { }
}

public class TestLogger : ILogger
{
    private readonly ITestOutputHelper? _testOutputHelper;

    public TestLogger(ITestOutputHelper? testOutputHelper = null)
    {
        _testOutputHelper = testOutputHelper;
    }

    public List<string> Messages { get; set; } = new();

    IDisposable ILogger.BeginScope<TState>(TState state) => NoopDisposable.Instance;

    bool ILogger.IsEnabled(LogLevel logLevel) => true;

    void ILogger.Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func<TState, Exception?, string> formatter)
    {
        var message = formatter(state, exception);
        LogLine($"[{logLevel}] {message}");
        if (exception != null)
        {
            LogLine(exception.ToString());
        }
    }

    private void LogLine(string message)
    {
        Messages.Add(message); // For assert statements in your unit test methods.
        if (_testOutputHelper != null)
        {
            _testOutputHelper?.WriteLine(message); // Test Explorer Standard Output.
            Console.WriteLine(message); // Google Cloud Build Log.
        }
    }

    private class NoopDisposable : IDisposable
    {
        public static readonly NoopDisposable Instance = new();
        public void Dispose() { }
    }
}

Example usage:

using Xunit.Abstractions;

public class MyTestClass
{
    private readonly ITestOutputHelper _testOutputHelper;

    public MyTestClass(ITestOutputHelper testOutputHelper)
    {
        _testOutputHelper = testOutputHelper;
    }

    [Fact]
    public void Test1()
    {
        var logger = new TestLogger(_testOutputHelper);
        logger.LogInformation("Hello world!");
    }

    [Fact]
    public void Test2()
    {
        var logger = new TestLogger<PleaseInjectMyLogger>(_testOutputHelper);
        var x = new PleaseInjectMyLogger(logger);
        x.Execute();
        var logMessage = Assert.Single(logger.Messages);
        Assert.Equal("Hooray!", logMessage);
    }
}

Sources:

Upvotes: 0

Andrew
Andrew

Reputation: 221

You have to use a logger that's directed at ITestOutputHelper. Details here: .net core 2.0 ConfigureLogging xunit test

If you are Ok with a third party here are two that will simplify the solution: https://blog.martincostello.com/writing-logs-to-xunit-test-output/ https://www.neovolve.com/2018/06/01/ilogger-for-xunit/

Upvotes: 2

William Jockusch
William Jockusch

Reputation: 27325

At present, I'm not aware of any sane way to do this.

If you really need debugging code whose output is visible from the Azure run, you can do something hackish like the following in your test.

[Fact]
public void LogDebugInfo() {
  var outputString = $"my debug info here";
  Assert.Equal($"", outputString);
}

Obviously this will cause the test to fail, but at least you can see your debug info.

Upvotes: 0

Related Questions