microsoftgraph / msgraph-sdk-dotnet

Microsoft Graph Client Library for .NET!
https://graph.microsoft.com
Other
695 stars 246 forks source link

[Client bug]: V5 Unit test extension posts #1843

Open eklopfstein opened 1 year ago

eklopfstein commented 1 year ago

Describe the bug Converting a project that was using V4 of the beta to V5 of the non beta. I encountered an issue where we are testing the creation of a user. I was able to mock the creation of the user just fine but then when I tried to mock the creation of an OpenTypeExtension on the user that was just created, I got a NullReferenceException.

To Reproduce

  1. Mocked client

    var mockRequestAdapter = new Mock<IRequestAdapter>();
    var client = new GraphServiceClient(mockRequestAdapter.Object);
    ...
    mockRequestAdapter.Setup(
                adapter => adapter.SerializationWriterFactory.GetSerializationWriter(It.IsAny<string>())
            ).Returns(new JsonSerializationWriter());
  2. Mock user creation (works)

    
    var user = new User
    {
    ...
    }
    mockRequestAdapter.Setup(
                adapter => adapter.SendAsync(It.IsAny<RequestInformation>(), User.CreateFromDiscriminatorValue, It.IsAny<Dictionary<string, ParsableFactory<IParsable>>>(), It.IsAny<CancellationToken>())
            ).ReturnsAsync(user);

2. Mock extension creation (failed)

var mockExtension = new OpenTypeExtension(); var mockAdditionalData = new Dictionary<string, object> { ... }; mockExtension.AdditionalData = mockAdditionalData;

mockRequestAdapter.Setup( adapter => adapter.SendAsync(It.IsAny(), OpenTypeExtension.CreateFromDiscriminatorValue, It.IsAny<Dictionary<string, ParsableFactory>>(), It.IsAny()) ).ReturnsAsync(mockExtension);


3. Run test that creates a user and then tries to add an extension

var result = await client.Users.PostAsync(user).ConfigureAwait(false); (works)

var extension = new OpenTypeExtension { ... }; var extensionResult = await client.Users[result.Id].Extenstions.PostAsync(extension); (fails)

Using the debugger I was able to verify that result is probably populated with mocked user response. 
Throws the error once that last line is executed, result.Id and extension and properly populated.

** Error **

Message:  System.NullReferenceException : Object reference not set to an instance of an object.

Stack Trace:  Utf8JsonWriter.FirstCallToGetMemory(Int32 requiredSize) Utf8JsonWriter.Grow(Int32 requiredSize) Utf8JsonWriter.WriteStartMinimized(Byte token) Utf8JsonWriter.WriteStartSlow(Byte token) Utf8JsonWriter.WriteStart(Byte token) JsonSerializationWriter.WriteObjectValue[T](String key, T value, IParsable[] additionalValuesToMerge) RequestInformation.SetContentFromParsable[T](IRequestAdapter requestAdapter, String contentType, T item) ExtensionsRequestBuilder.ToPostRequestInformation(Extension body, Action1 requestConfiguration) ExtensionsRequestBuilder.PostAsync(Extension body, Action1 requestConfiguration, CancellationToken cancellationToken) GraphService.CreateUserInGraph(User user) line 628 GraphService.CreateUser(User user) line 158 PostUserTests.CreateUserAndAddExtension() line 191 --- End of stack trace from previous location ---



**Expected behavior**
Expected `await client.Users[result.Id].Extenstions.PostAsync(extension);` to return the mocked extension setup in the test method.

**Client version**
Microsoft.Graph 5.5.0

**Additional context**
When actually running the app, I am able to run this code, create a user, and add the extension without problems. It seems to be how I am mocking the return value of `await client.Users[result.Id].Extenstions.PostAsync(extension);` that is not working.
aDavidaIsNoOne commented 1 year ago

I am experiencing the same issue and am severely blocked. Any assistance here would be greatly appreciated.

andrueastman commented 1 year ago

The API method await client. Users[result.Id].Extenstions.PostAsync(extension) is defined to return the base type of Extension and will downcast to OpenTypeExtension if the return value is indeed of that type.

Any chance this works out if you change OpenTypeExtension.CreateFromDiscriminatorValue to Extension.CreateFromDiscriminatorValue to match the API return type in the mock method you defined as below?

mockRequestAdapter.Setup(
                adapter => adapter.SendAsync(It.IsAny<RequestInformation>(), OpenTypeExtension.CreateFromDiscriminatorValue, It.IsAny<Dictionary<string, ParsableFactory<IParsable>>>(), It.IsAny<CancellationToken>())
            ).ReturnsAsync(mockExtension);
eklopfstein commented 1 year ago

@andrueastman After making your suggested edit it still seems to fail to mock the return value of await client. Users[result.Id].Extenstions.PostAsync(extension) as the error remains unchanged. and the mocked extension from the test file is still not returned.

andrueastman commented 1 year ago

@eklopfstein Any chance you could share the entire code sample/function that throws the error? From our end, the function below would run without issue.

        [Fact]
        public async Task PostExtensionAsync()
        {
            var mockRequestAdapter = new Mock<IRequestAdapter>();
            var graphServiceClient = new GraphServiceClient(mockRequestAdapter.Object);

            var mockExtension = new OpenTypeExtension
            {
                Id = "valid-id"
            };

            mockRequestAdapter.Setup(
                adapter => adapter.SerializationWriterFactory.GetSerializationWriter(It.IsAny<string>())
            ).Returns(new JsonSerializationWriter());
            mockRequestAdapter.Setup(
                adapter => adapter.SendAsync(It.IsAny<RequestInformation>(), Extension.CreateFromDiscriminatorValue, It.IsAny<Dictionary<string, ParsableFactory<IParsable>>>(), It.IsAny<CancellationToken>())
            ).ReturnsAsync(mockExtension);

            var extensionResult = await graphServiceClient.Users["test-id"].Extensions.PostAsync(mockExtension);
            Assert.Equal("valid-id", extensionResult.Id);
        }
eklopfstein commented 1 year ago

Tests code

{

            // arrange
            var user = new CompanyUser

            {

                Email = "fake@example.com",

                FirstName = "Fake",

                IsAccountEnabled = true,

                ObjectId = string.Empty,

                LastName = "User",

                UserPrincipalName = "fake@domain.com",

                Title = "Job",

                UserType = UserType.Guest,

                GuestType = GuestType.Site

            };

            var graphUser = new User

            {

                AccountEnabled = true,

                Id = Guid.NewGuid().ToString(),

                UserPrincipalName = "fake@domain.com",

                UserType = UserType.Guest,

                GivenName = "Fake",

                Surname = "User",

                JobTitle = "Job",

                OtherMails = new List<string> { "fake@example.com" },

                Extensions = new List<Extension> { }

            };

            mockLdapService.Setup(ls => ls.GetUser(It.IsAny<string>())).Returns<LdapUser>(null);

            mockRequestAdapter.Setup(

                adapter => adapter.SendAsync(It.IsAny<RequestInformation>(), User.CreateFromDiscriminatorValue, It.IsAny<Dictionary<string, ParsableFactory<IParsable>>>(), It.IsAny<CancellationToken>())

            ).ReturnsAsync(graphUser);

            var mockExtension = new OpenTypeExtension();

            var mockAdditionalData = new Dictionary<string, object>

            {

                { "GuestType", GuestType.Site }

            };

            mockExtension.AdditionalData = mockAdditionalData;

            mockRequestAdapter.Setup(

                adapter => adapter.SendAsync(It.IsAny<RequestInformation>(), Extension.CreateFromDiscriminatorValue, It.IsAny<Dictionary<string, ParsableFactory<IParsable>>>(), It.IsAny<CancellationToken>())

            ).ReturnsAsync(mockExtension);

            mockRequestAdapter.Setup(

                adapter => adapter.SerializationWriterFactory.GetSerializationWriter(It.IsAny<string>())

            ).Returns(new JsonSerializationWriter());

            // act

            var result = await graphService.CreateUser(CompanyUser).ConfigureAwait(false);

            // assert

            Assert.Equal(CompanyUserCreateStatus.Success, result.Value);

            mockLogger.Verify(l => l.Log(LogLevel.Information, It.IsAny<EventId>(), It.IsAny<It.IsAnyType>(), It.IsAny<Exception>(), It.IsAny<Func<It.IsAnyType, Exception?, string>>()), Times.Once);

        }

Code called by test

Part 1, create user


string userName = GetUsernameFromUserPrincipalName(user.UserPrincipalName);

            var graphUser = new User

            {

                AccountEnabled = user.IsAccountEnabled,

                DisplayName = $"{user.FirstName} {user.LastName}",

                GivenName = user.FirstName,

                JobTitle = user.Title,

                MailNickname = userName,

                OfficeLocation = user.Organizations != null ? string.Join(",", user.Organizations) : null,

                OtherMails = string.IsNullOrEmpty(user.Email) ? null : new List<string> { user.Email },

                PasswordProfile = new PasswordProfile()

                {

                    Password = !string.IsNullOrEmpty(user.Password) ? user.Password : Guid.NewGuid().ToString(), 

                    ForceChangePasswordNextSignIn = true

                },

                Surname = user.LastName,

                UserPrincipalName = user.UserPrincipalName,

                UserType = user.UserType

            };

            try

            {

                var result = await client.Users.PostAsync(graphUser).ConfigureAwait(false);

                string? guestType = null;

var result is succesfully set to the user mocked in the test class.

Part 2, add extension

                if (user.GuestType != null)

                {

                    var extension = new OpenTypeExtension

                    {

                        OdataType = "microsoft.graph.openTypeExtension",

                        ExtensionName = configuration.GetValue<string>("ExtensionProperties:GuestType"),

                        AdditionalData = new Dictionary<string, object>

                        {

                            { "GuestType", user.GuestType }

                        }

                    };

                    var extensionResult = await client.Users[result.Id]

                        .Extensions

                        .PostAsync(extension);

The error occurs at the last line above. The result and extension are both correctly populated but when the code is ran in the test it throws the error I provided in the first post.