'C# Azure Function app - Moq Tests - Adding dependency injection fails with CS0311

I have the following function:

public class WidgetRequest {

   [FunctionName("CreateWidget")]
    public static async Task<IActionResult> CreateWidget(
        [HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = "/widget")] HttpRequest req,
         [Queue("widgets"), StorageAccount("StorageForWidgets")] ICollector<string> messageQueue,
        ILogger log)
    {           
        ProvisionedWidgetsRepository storage = new ProvisionedWidgetsRepository();
        WidgetResponse response = new WidgetResponse ();
        var content = await new StreamReader(req.Body).ReadToEndAsync();
        log.LogInformation($"Received following payload: {content}");

        var widgetRequest = JsonConvert.DeserializeObject<Widget>(content);
        if (widgetRequest .name != null){     
                //add the request to queue for processing
                messageQueue.Add(JsonConvert.SerializeObject(widgetRequest));   
                //also add to provisioned storage table             
                response = await storage.ProvisioningRequest(widgetRequest , req.HttpContext.Items["MS_AzureFunctionsRequestID"].ToString(), "enqueued");
            
        } 
        else {
            response.status = "Error: Invalid Request";
            response.requestId=null;
        }
        return new OkObjectResult(JsonConvert.SerializeObject(response));  
    }

I have the following test:

    [Fact]
    public async void widget_creation_requests_should_be_stored_in_queue(){
        var messageQueue = TestFactory.CreateAzureStorageQueue();
        var storageTable = TestFactory.CreateAzureStorageTable();
        var request = TestFactory.CreateWidgetRequest();
        var response = (OkObjectResult)await WidgetRequest.CreateWidget(request, messageQueue, logger);
        Assert.NotNull(response);
        Assert.True(((AzureStorageQueueTestClient<string>)messageQueue).Messages.Count > 0);
    }

The test fails because I don't know how to pass in the mock storage table that I'm spinning up in the test ("var storageTable"). As you can see in the actual function, I manually instantiate a new storage object like this:

 ProvisionedWidgetsRepository storage = new ProvisionedWidgetsRepository();

I don't pass it in. Any tips would be appreciated.
Thanks

EDIT 1

So I've refactored to move out the storage logic into a separate class with an interface to support dependency injection. The code with the main logic now has a constructor, and uses the new interface. It looks like this:

public class WidgetRequest {
    //Using Interface for storage so we can dependency inject for testing. 
    private IWidgetRepositoryController storage;        
    public WidgetRequest(IWidgetRepositoryController storage)
    {
        this.storage = storage;    
    }

   [FunctionName("CreateWidget")]
    public static async Task<IActionResult> CreateWidget(
        [HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = "/widget")] HttpRequest req,
         [Queue("widgets"), StorageAccount("StorageForWidgets")] ICollector<string> messageQueue,
        ILogger log)
    {           
        //ProvisionedWidgetsRepository storage = new ProvisionedWidgetsRepository();
        WidgetResponse response = new WidgetResponse ();
        var content = await new StreamReader(req.Body).ReadToEndAsync();
        log.LogInformation($"Received following payload: {content}");

        var widgetRequest = JsonConvert.DeserializeObject<Widget>(content);
        if (widgetRequest .name != null){     
                //add the request to queue for processing
                messageQueue.Add(JsonConvert.SerializeObject(widgetRequest));   
                //also add to provisioned storage table             
                response = await storage.AddToTable(widgetRequest , req.HttpContext.Items["MS_AzureFunctionsRequestID"].ToString(), "enqueued");
            
        } 
        else {
            response.status = "Error: Invalid Request";
            response.requestId=null;
        }
        return new OkObjectResult(JsonConvert.SerializeObject(response));  
    }

This is what the new Storage Controller class looks like, along with the interface:

public interface IWidgetRepositoryController
{
    Task<WidgetResponse> AddToTable(Widget widgetRequest,  string requestID, string partitionName);
}
public class WidgetRepositoryController
{        
    public async Task<WidgetResponse> AddToTable(Widget widgetRequest,  string requestID, string partitionName)
    {
        ProvisionedWidgetRepository storage = new ProvisionedWidgetRepository ();
        WidgetResponse response = await storage.ProvisioningRequest(widgetRequest, requestID, partitionName);
        return response;
    }

}

So far all the logic above seems to be ok - as in no errors. But I am now trying to create the Startup.cs file. I'm getting an error CS0311.

Here's the code:

 [assembly: FunctionsStartup(typeof(Az.Fn.Widgets.Startup))]
 namespace Az.Fn.Widget
 {

public class Startup: FunctionsStartup
{
    public override void Configure(IFunctionsHostBuilder builder)
    {
        builder.Services.AddTransient<IWidgetRepositoryController, WidgetRepositoryController>();
    }
}

The full error:

The type 'Az.Fn.Widgets.WidgetRepositoryController' cannot be used as type parameter 'TImplementation' in the generic type or method 'ServiceCollectionServiceExtensions.AddTransient<TService, TImplementation>(IServiceCollection)'. There is no implicit reference conversion from 'Az.Fn.Widgets.WidgetRepositoryController' to 'Az.Fn.Widgets.IWidgetRepositoryController'.



Solution 1:[1]

I had to refactor to support DI as per the comment from ThrowingSpoon, and then I had a bug. I had to change the WidgetRespositoryController class from this:

public class WorkspaceRepositoryController 

to this:

public class WorkspaceRepositoryController : IWorkspaceRepositoryController

And it seems to be happy.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1