Azure / azure-sdk-for-net

This repository is for active development of the Azure SDK for .NET. For consumers of the SDK we recommend visiting our public developer docs at https://learn.microsoft.com/dotnet/azure/ or our versioned developer docs at https://azure.github.io/azure-sdk-for-net.
MIT License
5.49k stars 4.81k forks source link

[QUERY] Mocking with ArmClient and CostManagementExportCollection #45697

Open jeremysimmons opened 2 months ago

jeremysimmons commented 2 months ago

Library name and version

Azure.ResourceManager.CostManagement 1.0.1

Query/Question

It is quite difficult to mock functionality related to the Cost Management api, specifically around the CostManagementExportResource.

Once you've instantiated the

According to the design guidelines, https://azure.github.io/azure-sdk/dotnet_introduction.html,

DO use instance methods instead of extension methods when defined in the same assembly. The instance methods are simpler to mock.

I'm assuming this is because ArmClient is in Azure.ResourceManager and CostManagementExtensions is in Azure.ResourceManager.CostManagement.

I would much rather have a CostManagementClient that takes an ArmClient as a parameter than have the current debacle.

Additionally this guidance seems to be missing completely.

(https://azure.github.io/azure-sdk/dotnet_introduction.html#dotnet-mocking-factory-builder) DO provide factory or builder for constructing model graphs returned from virtual service methods.

Maybe I'm missing it, but I wanted a simple/easy way for my business class to call. If I'm missing something obvious, please let me know.

var exports = armClient.GetCostManagementExports(scope).
exports.CreateOrUpdateAsync

Environment

dotnet core 6 VS 2022 v17.9.6

github-actions[bot] commented 2 months ago

Thanks for the feedback! We are routing this to the appropriate team for follow-up. cc @anthony-c-martin @calecarter @cemheren @j5lim @majastrz.

mcgallan commented 1 day ago

Hi @jeremysimmons, Thank you for your feedback. I have written a mock test for CostManagementExportCollection that works. After running it locally, it successfully executed. I hope this piece of code can provide you with some assistance.

using System;
using System.Threading.Tasks;
using Azure.Core;
using Azure.ResourceManager.CostManagement.Mocking;
using Azure.ResourceManager.CostManagement.Models;
using Azure.ResourceManager.Resources;
using Moq;
using NUnit.Framework;

namespace Azure.ResourceManager.CostManagement.Tests
{
    public class MockExportResource
    {
        [Test]
        public async Task Mocking_GetCollectionAndCreate()
        {
            #region mocking data
            var subscriptionId = Guid.NewGuid().ToString();
            var resourceGroupName = "myRg";
            var exportName = "myExport";
            var exportScope = $"/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}";
            var exportId = CostManagementExportResource.CreateResourceIdentifier(exportScope, "exportName");
            var exportData = ArmCostManagementModelFactory.CostManagementExportData(exportId, exportName);
            #endregion

            #region mocking setup
            var clientMock = new Mock<ArmClient>();
            var clientExtensionMock = new Mock<MockableCostManagementArmClient>();
            var rgMock = new Mock<ResourceGroupResource>();
            //for CostManagementExport
            var exportCollectionMock = new Mock<CostManagementExportCollection>();
            var exportMock = new Mock<CostManagementExportResource>();
            var exportLroMock = new Mock<ArmOperation<CostManagementExportResource>>();
            //set some data in the result
            exportMock.Setup(exportMock => exportMock.Id).Returns(exportId);
            exportMock.Setup(exportMock => exportMock.Data).Returns(exportData);
            // first mock: mock the same method in mocking extension class
            clientExtensionMock.Setup(e => e.GetCostManagementExports(new ResourceIdentifier(exportScope))).Returns(exportCollectionMock.Object);
            // second mock: mock the GetCachedClient method on the "extendee"
            clientMock.Setup(rg => rg.GetCachedClient(It.IsAny<Func<ArmClient, MockableCostManagementArmClient>>())).Returns(clientExtensionMock.Object);
            // setup the mock on the collection for CreateOrUpdate method
            exportCollectionMock.Setup(c => c.CreateOrUpdateAsync(WaitUntil.Completed, exportName, exportData, default)).ReturnsAsync(exportLroMock.Object);
            exportLroMock.Setup(lro => lro.Value).Returns(exportMock.Object);
            #endregion

            //the mocking test
            var client = clientMock.Object;
            var exportCollection = client.GetCostManagementExports(new ResourceIdentifier(exportScope));
            var exportlro = await exportCollection.CreateOrUpdateAsync(WaitUntil.Completed, exportName, exportData, default);
            var export = exportlro.Value;

            Assert.AreEqual(exportId, export.Id);
            Assert.AreEqual(exportName, export.Data.Name);
            Assert.AreEqual(exportData, export.Data);
        }
    }
}
github-actions[bot] commented 1 day ago

Hi @jeremysimmons. Thank you for opening this issue and giving us the opportunity to assist. To help our team better understand your issue and the details of your scenario please provide a response to the question asked above or the information requested above. This will help us more accurately address your issue.