'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 |
