5

I'm getting this error when I run the unit test: Error Message: System.ArgumentNullException : Value cannot be null. Parameter name: logger and also I'm getting this warning: Field 'OrderControllerTest._logger' is never assigned to, and will always have its default value null

Is there a way to silence the logger or test it?

Still new to programming and eager to learn. Have struggled with this for a long time. Appreciate your help and guidance.

Update with stack trace

Stack Trace:
   at Microsoft.Extensions.Logging.LoggerExtensions.Log(ILogger logger, LogLevel logLevel, EventId eventId, Exception exception, String message, Object[] args)
   at Microsoft.Extensions.Logging.LoggerExtensions.LogError(ILogger logger, Exception exception, String message, Object[] args)
   at Project.Controllers.OrderController.CustomerOrders(String customerId) in C:\projects\Project\Controllers\OrderController.cs:line 34
   at Tests.OrderControllerTest.AllOrdersForCustomer() in C:\projects\UnitTests\Project.Service.Tests\OrderControllerTest.cs:line 74

What I have so far:

OrderControllerTest.cs:

using System;
using System.Collections.Generic;
using NUnit.Framework;
using Project.Models;
using Project.Controllers;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;

namespace Tests
{
  [TestFixture]
  public class OrderControllerTest
  {
      ILogger<OrderController> _logger;

      public OrderControllerTest()
      {

      }

      [Test]
      public void AllOrdersForCustomer()
      {
        // Arrange
        var controller  = new OrderController(_logger);
        var expectedResult = new List<Order>();
        var oneOrder = new Order()
        {
            orderId = 228, 
            product= "Jeans",
            status = "Ready for shipping",
            price= 20$
        };

        var twoOrder = new Order()
        {
            caseId = 512, 
            creditorName = "Basketball",
            status = "Shipped",
            totalOpenBalance = 30$
        };
        expectedResult.Add(oneOrder);
        expectedResult.Add(twoOrder);

        // Act
        var actionResult = controller.CustomerOrders("1243");
        var result = (List<Order>)actionResult;

        // Assert
        for (var i = 0; i < result.Count; i++)
        {               
          CollectionAssert.AreEqual(expectedResult, result);
        }
    }
}

OrderController.cs:

using System;
using System.Collections.Generic;
using Project.Models;
using Project.Repositories;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;


namespace Project.Controllers
{
  public class OrderController : Controller
  {
    ILogger<OrderController> _logger;

    public OrderController(ILogger<OrderController> logger)
    {
        _logger = logger;
    }

    [HttpGet("customers/{customerId}/orders")]
    public List<Order> CustomerOrders(string customerId)
    {
        try
        {
            return OrderRepository.AllOrders(customerId);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Cant retrieve no orders.");
            return null;
        }
    }
  }
}

 ILogger<OrderController> _logger;

 public OrderControllerTest()
 {
    _logger = new Mock<ILogger<OrderController>>().Object;  
 }

OrderRepository.cs:

    public static List<Order> AllOrders(string customerId)
    {
        var orderlist = new List<Order>();


        if (Order!=null && Order.Count>0)
        {
            if (Order.TryGetValue(customerId, out orderlist))
                return orderlist;
        }
        return FromDB(customerId);

    }
Niknak
  • 583
  • 1
  • 8
  • 22
  • 3
    Error message is correct. You do not assign a value to that variable and it will remain null. If trying to test in isolation then assign a mocked logger to the variable. – Nkosi Jul 25 '18 at 19:48
  • 1
    `ILogger _logger;` in OrderControllerTest is never assigned to – JSteward Jul 25 '18 at 19:49
  • So it would be easy to guess that the `OrderController` throws with a null logger, but it doesn't seem to be in this code. Is this the exact code your using? and what is the stack trace of `ArgumentException` – JSteward Jul 25 '18 at 19:51
  • You also appear to be implementing poor design as `OrderRepository` looks like you are tightly coupling to static implementation concerns. – Nkosi Jul 25 '18 at 19:52
  • The sequence of events looks like you try to call `OrderRepository.AllOrders` and that craps out (exception) which then tries to call the logger which is ....wait for it....not initialized to anything. And bam. here we are – Nkosi Jul 25 '18 at 19:54
  • @JSteward I have update above, see at `Update with stack trace`, please. – Niknak Jul 25 '18 at 19:58
  • 1
    in ctor OrderControllerTest just use: _logger = new Mock>().Object; – komluk Jul 25 '18 at 20:00
  • This appears to be an [XY problem](https://meta.stackexchange.com/questions/66377/what-is-the-xy-problem). – Nkosi Jul 25 '18 at 20:01
  • @Nkosi Did you mean like that (see above, please)? I'm getting refference error. `The type or namespace name 'Mock<>' could not be found (are you missing a using directive or an assembly reference?)` – Niknak Jul 25 '18 at 20:13
  • @Niknak well if you want to use Moq you first have to add it to the project (Nuget) then reference it in the test class `using Moq;` – Nkosi Jul 25 '18 at 20:21
  • @Nkosi Sorry, forgot to say that I'm using vs code =p – Niknak Jul 25 '18 at 20:24
  • @komluk Did you mean like that (see above, please)? I'm getting refference error. The type or namespace name 'Mock<>' could not be found (are you missing a using directive or an assembly reference?) Tried to see Moq for extensions but no luck. – Niknak Jul 25 '18 at 20:27

2 Answers2

4

Assuming you have taken input from EL MOJO, you need to initialise your ILogger in your tests, using Moq:

Mock<ILogger<OrderController>> _logger;

public OrderControllerTest()
{
    _logger = new Mock<ILogger<OrderController>>();
    _logger.Setup(m => m.LogError(It.IsAny<Exception>(), It.IsAny<string>()));
}

Now you should initialise your controller with the following:

var controller  = new OrderController(_logger.Object);
Phil Golding
  • 433
  • 3
  • 10
  • I continued this on with Niknak on chat - discussed the tightly coupled logic with the order repository, and how it should be an interface rather than a static class/methods. Some points for Niknak: You'll essentially be doing a "poor mans" dependency injection through what we discussed - see: https://stackoverflow.com/questions/130794/what-is-dependency-injection Also see interfaces (And points on generics usually denoted as ): https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/interfaces/ – Phil Golding Jul 25 '18 at 22:55
1

In the constructor for your "OrderControllerTest" class, you need to create a mock of the ILogger interface. The Logger is a dependency of your OrderController and you don't want to test its functionality within a unit test for your controller.

There are many good mocking frameworks out there. I prefer Moq but there are many others. You would then need to mock the methods that your OrderController is utilizing.

Moq Example:

using ...
using Moq;

namespace Tests
{
  [TestFixture]
  public class OrderControllerTest
  {
    public OrderControllerTest()
    {
      var mockLogger = new Mock<ILogger<OrderController>>();
      mockLogger.Setup( x => x.LogError( It.IsAny<Exception>(), It.IsAny<string>() );
      _logger = mockLogger.Object;
    }
  ...
  }
}
EL MOJO
  • 783
  • 1
  • 9
  • 22
  • Forgot to tell you that I'm using vs code. I can not seem to download Moq library in vs code. – Niknak Jul 25 '18 at 20:42
  • It's available as a nuGet package [here](https://www.nuget.org/packages/moq/). Or just add to your test *.csproj file within an section. – EL MOJO Jul 25 '18 at 20:48
  • Or from the terminal within VS Code type 'dotnet add package Moq', put the name of your test project within the place holder. – EL MOJO Jul 25 '18 at 21:01
  • Thx it helped with adding ref in *.csproj, but now i get a red underline in `Mock>()` and for the variable `_logger `. – Niknak Jul 25 '18 at 21:03
  • Error says `Non-invocable member 'Mock' cannot be used like a method.` and `The name '_logger' does not exist in the current context` – Niknak Jul 25 '18 at 21:04
  • Sorry, I forgot to add the "new" keyword. Did you add that? I edited my response to include it. – EL MOJO Jul 25 '18 at 21:08
  • Yeah I did now. Still red underline in `_logger` =p – Niknak Jul 25 '18 at 21:11
  • FYI, your OrderRepository is also a dependency and should be injected and mocked as well. You should then have a mocked instance of AllOrders which will throw and exception so you can test your catch block. – EL MOJO Jul 25 '18 at 21:14
  • I have added OrderRepository above, could you take a look, please. I don't get how to test the catch block. – Niknak Jul 25 '18 at 21:19
  • 1
    You need to create an interface for the OrderRepository and inject it into your Controller constructor. This is a separate issue and should probably be another question so as to not confuse your original issue. – EL MOJO Jul 25 '18 at 21:26
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/176755/discussion-between-niknak-and-el-mojo). – Niknak Jul 25 '18 at 21:35