Unable to mock the response to an SfDataManager

I'm trying to write a unit test for a grid that uses an SfDataManager, so I have to send a 'manual' response like the ones received from Web API controllers.  
I have a controller returning a list of Friend, that works perfectly with the grid I'm using.  
Now, when I'm mocking the HttpClient call, I cannot find the right representation to pass data back to the SfDataManager.  
The best I could find (probably the closest) is code like:  
  
MockHttpMessageHandler mock = ctx.Services.AddMockHttpClient("https://localhost:44351");
List callResult = new()
{
new Friend { Id = 1.ToGuid(), Name = "Andrea Bioli", City = "Rome", Kind = Friend.RelationshipKind.Good, Pets = new List() },
new Friend { Id = 2.ToGuid(), Name = "Andrea Bioli", City = "Rome", Kind = Friend.RelationshipKind.Good, Pets = new List() }
};
HttpResponseMessage msg = new(HttpStatusCode.OK)
{
Content = JsonContent.Create(JsonSerializer.Serialize(callResult))
};
mock.When("/api/friends/friends-for-grid").Respond(req => msg);  
  
This makes the grid go in error, caught by:  
  
OnActionFailure="@ActionFailure"  
  
in the GridEvents component.  

The error message I see there is:  

Error converting value "[{"Id":"00000000-0000-0000-0000-000000000001","Name":"Andrea Bioli","City":"Rome","Kind":3,"Pets":[],"NPets":0},{"Id":"00000000-0000-0000-0000-000000000002","Name":"Andrea Bioli","City":"Rome","Kind":3,"Pets":[],"NPets":0}]" to type 'Syncfusion.Blazor.Data.UrlResult`1[DoNothing.Shared.Friend]'. Path '', line 1, position 405.  

But now I'm stuck, since UrlResult<> is not a publicly accessible class, and I don't find any documentation about it.  
Probably the big difference is that my controller class returns an ActionResult (that makes the grid work), while the mock HttpClient I'm using (RichardSzalay.MockHttp, as suggested by support in another thread) wants an HttpResponseMessage, that I cannot build correctly.  
Could you provide a simple example, please?  
Thanks in advance  
AB  


20 Replies 1 reply marked as answer

VN Vignesh Natarajan Syncfusion Team December 4, 2020 04:10 AM UTC

Hi Andrea,  

Thanks for contacting Syncfusion support.  

Query: “But now I'm stuck, since UrlResult<> is not a publicly accessible class, and I don't find any documentation about it.   

We have analyzed your query and we understand that you are facing trouble while mock testing the data returned from SfDatamanager. Kindly modify your code example as below to resolve your query. Since data is returned in form of result and count from DataManager.  

Refer the below modified code example.  

MockHttpMessageHandler mock = ctx.Services.AddMockHttpClient("https://localhost:44351"); 
            List callResult = new() 
            { 
                new Friend { Id = 1.ToGuid(), Name = "Andrea Bioli", City = "Rome", Kind = Friend.RelationshipKind.Good, Pets = new List() }, 
                new Friend { Id = 2.ToGuid(), Name = "Andrea Bioli", City = "Rome", Kind = Friend.RelationshipKind.Good, Pets = new List() } 
            }; 
            HttpResponseMessage msg = new(HttpStatusCode.OK) 
            { 
                Content = JsonContent.Create(JsonSerializer.Serialize(new { result = callResult, count = callResult.Count })) 
            }; 
            mock.When("/api/friends/friends-for-grid").Respond(req => msg); 


Please get back to us if you have further queries.  

Regards, 
Vignesh Natarajan 



AB Andrea Bioli December 7, 2020 07:15 AM UTC

Thanks, but it still gives an error:

Error converting value "{"result":[{"Id":"00000000-0000-0000-0000-000000000001","Name":"Andrea Bioli","City":"Rome","Kind":3,"Pets":[],"NPets":0},{"Id":"00000000-0000-0000-0000-000000000002","Name":"Andrea Bioli","City":"Rome","Kind":3,"Pets":[],"NPets":0}],"count":2}" to type 'Syncfusion.Blazor.Data.UrlResult`1[DoNothing.Shared.Friend]'. Path '', line 1, position 446.


VN Vignesh Natarajan Syncfusion Team December 14, 2020 12:43 PM UTC

Hi Andrea,  
 
Sorry for the delay in getting back to you.  
 
Query: “but it still gives an error: 
 
We have analyzed the reported query at our end and the reported issue will occur due to serialization. It will be very helpful for us to analyze the reported issue with issue reproducible sample. So kindly share the issue reproducible sample to validate the reported issue at our end and provide the solution as early as possible.  
 
Regards, 
Vignesh Natarajan 



AB Andrea Bioli December 14, 2020 04:08 PM UTC

I'm writing a complex application, it's not easy to extract quickly an executable sample.
Anyway, I simplified the problem to a data class like:

    public class Friend
    {
        public string Name { get; set; }
    }

a Grid to this minimum:

<SfGrid @ref="Grid" ID="Grid" TValue="Friend">
  <SfDataManager Url="/api/friends/friends-for-grid" Adaptor="Adaptors.UrlAdaptor"></SfDataManager>
  <GridEvents TValue="Friend" OnActionFailure="@ActionFailure"></GridEvents>
  <GridColumns>
    <GridColumn Field="Name" HeaderText="Name" Width="90" FilterSettings="@(new FilterSettings{ Operator = Operator.Contains })"></GridColumn>
  </GridColumns>
</SfGrid>

and the test like:

            MockJSRuntimeInvokeHandler JSRuntimeMock = null;

            // Arrange
            using var ctx = new TestContext();

            ctx.Services.AddSyncfusionBlazor();
            // JSRuntimeMock = ctx.Services.AddMockJSRuntime(JSRuntimeMockMode.Strict);
            JSRuntimeMock = ctx.Services.AddMockJSRuntime();
            if (JSRuntimeMock != null)
            {
                JSRuntimeMock.Setup<bool>("sfBlazor.isRendered", new { }).SetResult(true);
            }

            Mock<IApiCall> mockApiCall = new();
            ctx.Services.Add(new ServiceDescriptor(typeof(IApiCall), mockApiCall.Object));

            MockHttpMessageHandler mock = ctx.Services.AddMockHttpClient("https://localhost:44351");
            List<Friend> callResult = new()
            {
                new Friend { Name = "Andrea"},
                new Friend { Name = "Mario"}
            };

            HttpResponseMessage msg = new(HttpStatusCode.OK)
            {
                Content = JsonContent.Create(JsonSerializer.Serialize(new { result = callResult, count = callResult.Count }))
            };
            mock.When("/api/friends/friends-for-grid").Respond(req => msg);

            // Act
            var cut = ctx.RenderComponent<FriendsGrid>();
            await Task.Delay(2000);

            // Assert
            var count = cut.FindAll(".e-row").Count;
            Assert.True(2 == count, "Number of rows not generated properly");

            cut.MarkupMatches("<h1>Hello world from Blazor</h1>");

During the Task.Delay(2000) call, I get the exception, with these details:

Message: Error converting value "{"result":[{"Name":"Andrea"},{"Name":"Mario"}],"count":2}" to type 'Syncfusion.Blazor.Data.UrlResult`1[DoNothing.Shared.Friend]'. Path '', line 1, position 119.
InnerException: Could not cast or convert from System.String to Syncfusion.Blazor.Data.UrlResult`1[DoNothing.Shared.Friend].

It looks like the error happens anyway, even with this simple data class, so it should be really easy for you to replicate this behaviour...
I tried many variations, but it always just gets down to deserialize the input data.
Could you share the UrlResult<> source code?
Thanks
Andrea




VN Vignesh Natarajan Syncfusion Team December 22, 2020 11:01 AM UTC

Hi Andrea,  

Thanks for the udpate. Sorry for the delay in getting back to you.  

Currently we are preparing a sample as per your requirement. We will update you the further details by 24th December 2020.  

Till then we appreciate your patience.  

Regards, 
Vignesh Natarajan 



AB Andrea Bioli January 11, 2021 11:33 PM UTC

...ehm... :-)


VN Vignesh Natarajan Syncfusion Team January 13, 2021 11:51 AM UTC

Hi Andrea, 

Sorry for the delay in getting back to you. We regret for the inconvenience caused.   

We have tried to prepare a sample using your code example but we are facing difficulties with overridden method of AddMockHttpClient in test case. So kindly share details about the AddMockHttpClient method along with following details.  

  1. Share your application type (Blazor Server or Blazor Wasm).
  2. Share Syncfusion.Blazor Nuget version package.
 
Above requested details will be helpful for us to prepare a sample as per your code and provide solution as early as possible.  

Regards, 
Vignesh Natarajan  




AB Andrea Bioli January 13, 2021 12:13 PM UTC

I think it was some of you, to point me in the right direction. :-) Anyway, I used RichardSzalay.MockHttp.
I then created this support class:

    public static class MockHttpClientBunitHelpers
    {
        public static MockHttpMessageHandler AddMockHttpClient(this TestServiceProvider services, string baseAddress)
        {
            var mockHttpHandler = new MockHttpMessageHandler();
            var httpClient = mockHttpHandler.ToHttpClient();
            httpClient.BaseAddress = new Uri(baseAddress);
            services.AddSingleton<HttpClient>(httpClient);
            return mockHttpHandler;
        }

        public static MockedRequest RespondJson<T>(this MockedRequest request, T content)
        {
            request.Respond(req =>
            {
                var response = new HttpResponseMessage(HttpStatusCode.OK)
                {
                    Content = new StringContent(JsonSerializer.Serialize(content))
                };
                response.Content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
                return response;
            });
            return request;
        }

        public static MockedRequest RespondJson<T>(this MockedRequest request, Func<T> contentProvider)
        {
            request.Respond(req =>
            {
                var response = new HttpResponseMessage(HttpStatusCode.OK)
                {
                    Content = new StringContent(JsonSerializer.Serialize(contentProvider()))
                };
                response.Content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
                return response;
            });
            return request;
        }
    }

 I hope this helps.

P.S.: so you really never created unit tests for your code? :-) Joking...


VN Vignesh Natarajan Syncfusion Team January 20, 2021 03:45 AM UTC

Hi Andrea,  
  
Sorry for the delay in getting back to you. Thanks for sharing the requested details.  
  
We have prepared a sample as per your suggestion with provided code example and we are able to reproduce the reported issue at our end also. This is because we have handled the response returned from the service differently. Refer the below modified code example.  
  
[TestMethod] 
       public async Task SyncfusionGrid() 
       { 
           MockJsRuntimeInvokeHandler JSRuntimeMock = null;  
           // Arrange 
           using var ctx = new Tests();  
           ctx.Services.AddScoped<NavigationManagerMockNavigationManager>(); 
           ctx.Services.AddSyncfusionBlazor(); 
            
           // Act 
           JSRuntimeMock = ctx.Services.AddMockJsRuntime(); 
           if (JSRuntimeMock != null) 
           { 
               JSRuntimeMock.Setup<bool>("sfBlazor.isRendered"new { }).SetResult(true); 
           }  
                       
           List <BusinessObjectcallResult = new List<BusinessObject>() 
           { 
               new BusinessObject{ TaskName = "Andrea" }, 
               new BusinessObject { TaskName = "Mario" } 
           };  
           HttpResponseMessage msg = new HttpResponseMessage(HttpStatusCode.OK) 
           { 
               Content = new StringContent(JsonSerializer.Serialize(new UrlResult<BusinessObject>() { Result = callResult, Count = callResult.Count }))                           
           };            
           mock.When("http://localhost:13387/api/default").Respond(req => msg);           
           var cut = ctx.RenderComponent<SyncfusionGrid>();            
           await Task.Delay(2000);  
           // Assert 
           var count = cut.FindAll(".e-row").Count;            
           Assert.IsTrue(2 == count, "Number of rows not generated properly");          
       } 
  
[ServerSide.Data] 
public class UrlResult<T>   {       [JsonProperty("result")]       public List<T> Result { getset; }        [JsonProperty("count")]       public int Count { getset; }       [JsonProperty("aggregates")]       public IDictionary<stringobject> Aggregates { getset; }   }
  
For your convenience we have prepared a sample using above solution. Kindly download the sample from below  
  
  
Please get back to us if you have further queries.  
  
Regards, 
Vignesh Natarajan 


Marked as answer

AB Andrea Bioli February 7, 2021 04:42 PM UTC

The sample works, and I can see now what I had to update on my code. Thank you for the support.
I have to say, anyway, that looking at your sample, I see the class UrlResult that's used to create the mocked result in the main project, and moreover that uses the Newtonsof.Json JsonPropertyAttribute, while in the test project that uses it you use the new System.Text.Json JsonSerializer. Are you sure that is's a proper approach? Just your opinion...
Thanks a lot.
Andrea Bioli


VN Vignesh Natarajan Syncfusion Team February 8, 2021 07:17 AM UTC

Hi Andrea, 
 
Thanks for the update.  
 
Query: “Are you sure that is's a proper approach? Just your opinion... 
 
Yes, you can use above suggested approach to Mock test the data in Grid. These serializers (NewtonSoft and Json) are used to convert the object of UrlResult class to string. There wont be any problem while using the above approach.  
 
Please get back to us if you have further queries.      
 
Regards, 
Vignesh Natarajan 



AP Aditi Patre replied to Vignesh Natarajan July 3, 2021 01:23 PM UTC

@Vignesh Natarajan

Is something similar available for ASP.net core MVC syncfusion. I cannot find any examples for the same.



PG Praveenkumar Gajendiran Syncfusion Team July 5, 2021 12:47 PM UTC

Hi Aditi,

Greetings from Syncfusion support. 
Based on your query, we suggest you to refer the below documentation to get start with the ASP.NET Core Grid control and to bind dataSource to the Grid properly. 
 

Still if you are facing any difficult, could you please share the below details, which will be helpful for us to validate further about issue. 

  1. Please share the exact problem are you facing at your end.
  2. Please confirm that you have referred the DefaultContractResolver in the startup page(to resolve camel casing issue.) If not please refer the below documentation to do so.
  1. Please share your complete code example and Grid and scripts(event code) and controller side code
  2. Let us know the replication procedure for reproducing the problem with Video demonstration to understand it better.
  3. If possible share us a simple sample to replicate the problem. It would be helpful to identify your problem case better so that we can check and provide the solution based on that
  4. Please share the screenshot or video demo of the issue.

Regards, 
Praveenkumar G 



MA Marco March 29, 2022 08:24 PM UTC

Hi Support!


I have a similar issue and I'm trying the above solution, but it is not working for me.

I put breakpoints on DataBound and ActionFailure events on my grid but none of them is being called.

Also I tried to use MockHttpMessageHandler's GetMatchCount method, but it always returns zero. It seems to me Grid's DataManager HttpClient is not being called at all, as no rows are being appended to my grid.

My setup is very similar to the above, except that I send additional parameters to the server by using a Query instance.


This Query is populated in OnInitializedAsync method.


Please let me know if you require further details.

Any help is highly appreciated.


Best regards,

Marco.



RS Renjith Singh Rajendran Syncfusion Team March 30, 2022 08:21 AM UTC

Hi Marco,


Query : I put breakpoints on DataBound and ActionFailure events on my grid but none of them is being called. It seems to me Grid's DataManager HttpClient is not being called at all, as no rows are being appended to my grid.

We are not clear about the exact scenario you are facing the reported problem. We suggest you to ensure to have defined the SfDataManager as like our below documentation to bind remote data to Grid.

https://blazor.syncfusion.com/documentation/datagrid/data-binding#remote-data


If you are still facing difficulties then, we need the following details to further procced on this scenario,


  1. Share the complete Grid rendering codes.
  2. Share the details of adaptor and service you are using to fetch data to Grid.
  3. Share the details of any exception you have faced while running your application.
  4. Share a video demo showing the replication of the issue you are facing.
  5. If possible share a simple issue reproducing sample for us to validate based on your scenario.


The provided information will help us analyze the problem, and provide you a solution as early as possible.


Regards,

Renjith R



MA Marco replied to Renjith Singh Rajendran April 23, 2022 11:00 PM UTC

Hi  Renjith,


Sorry for the late reply.


Please find below details about my Grid, Adaptor and the Service:

<SfGrid @ref="_grid" TValue="Response" ID="Grid" Query="@_gridQuery"

Height="100%" AllowPaging="true" AllowSorting="true" Toolbar="@(new [] { "Search" })">
<SfDataManager Adaptor="Adaptors.CustomAdaptor" EnableCaching="true">
<CustomWebApiAdaptor />
</SfDataManager>
<GridEvents TValue="Response" DataBound="OnGridDataBound" OnActionBegin="OnGridActionBegin"
OnActionFailure="OnGridActionFailure" RowSelected="OnGridRowSelectedAsync" />
... (Sort, Search and paging settings -- omitted for brevity)
<GridColumns>
<GridColumn Field=@nameof(Response.Id) HeaderText="Id" IsIdentity="true" AllowSearching="false" Visible="false" />
<GridColumn Field=@nameof(Response.Name) HeaderText="Nome" Width="120" />
<GridColumn Field=@nameof(Response.FullAddress) HeaderText="Endereço" HideAtMedia="(min-width: 600px)" Width="300" />
</GridColumns>
</SfGrid>



public class CustomWebApiAdaptor : DataAdaptor<DataService>
{
    public override async Task<object> ReadAsync(DataManagerRequest dm, string? key = null)
    {
        ArgumentNullException.ThrowIfNull(dm, nameof(dm));


        var dataSource = await Service.ListAsync(new GetDataList { QueryOptions = dm.ToODataOptions() });
        if (dataSource is null)
        {
            throw new InvalidOperationException("Retorno da API não pode ser nulo.");
        }


        return dm.RequiresCounts
            ? new DataResult() { Result = dataSource.Items, Count = dataSource.Count }
            : dataSource.Items;
    }
}



public class DataService
{
    private readonly HttpClient _httpClient;


    public DataService(HttpClient httpClient)
    {
        _httpClient = httpClient;
    }


    public virtual async Task<ApiListResponse<Response>?> ListAsync(GetDataList request)
    {
        var url = $"Data?{request.QueryOptions}";
        var result = await _httpClient.GetAsync(url);
        result.EnsureSuccessStatusCode();
        string responseBody = await result.Content.ReadAsStringAsync();
        return JsonSerializer.Deserialize<ApiListResponse<Response>>(responseBody, GetJsonSerializerOptions());
    }


private static JsonSerializerOptions GetJsonSerializerOptions()
    {
...
}
}


Here is my test case:

[Theory, AutoData]
public async void ShouldRenderComponentPopulatingGrid(Guid id1, Guid id2)
{
MockHttpMessageHandler mock = Services.AddMockHttpClient();
var callResult = new List<Response>()
{
new() { Id = id1, Name = "Item 1" },
new() { Id = id2, Name = "Item 2" }
};
HttpResponseMessage respMsg = new(HttpStatusCode.OK)
{
Content = JsonContent.Create(JsonSerializer.Serialize(new { Result = callResult, Count = callResult.Count }))
};
var request = mock.When("Data*").Respond(req => respMsg);


var cut = Render(@<GridComponent />);


var sfGrid = cut.FindComponent<SfGrid<Response>>();


var matches = mock.GetMatchCount(request); // Always returns zero
}



Any help is appreciated.


Best regards,

Marco.



RN Rahul Narayanasamy Syncfusion Team April 26, 2022 02:13 PM UTC

Hi Marco,


Thanks for the update.


We are currently checking the reported query from our end and we will update the further details within two business days. Until then we appreciate your patience.


Regards,

Rahul



RS Renjith Singh Rajendran Syncfusion Team April 29, 2022 07:11 AM UTC

Hi Marco,


We suspect that there might be some problem with your way of rendering CustomAdaptor as component which might have caused the reported problem. So ,we suggest you to refer to the below documentations for more details on rendering CustomAdaptor as component.

https://blazor.syncfusion.com/documentation/datagrid/custom-binding#custom-adaptor-as-component


We have also prepared a sample by rendering CustomAdaptor as component and fetching data from service to bind in Grid. Please download and refer the sample from the link below,

Sample : https://www.syncfusion.com/downloads/support/directtrac/general/ze/ServerApp-826918869


Please refer the above sample and reference documentation and check this from your side. If you are still facing difficulties, then kindly get back to us with an issue reproducing sample based on your scenario for us to proceed further.


Regards,

Renjith R



MA Marco April 29, 2022 10:31 PM UTC

Hi Renjith ,


Thanks for the reply.

However, by taking a thorough look at the sample provided in this reply I managed to find the issues.


1) I did not add the DataService to my service providers collection in my test case (duh...)

2)  The BaseUri of my mocked HttpClient was not matching the BaseUri in my tests (duh again...)


Fixing the two items above, it seems to be working fine.


Nevertheless, I was intrigued by the following piece of code in the sample:

protected override async Task OnAfterRenderAsync(bool firstRender)
{
   if (firstRender)
   {
      CustomersIpinsTreeGrid.Refresh();
   }
}


This refresh is not really necessary when the application is executed, and, even the unit test works fine when I comment it, EXCEPT if I have a column with the HideAtMedia clause configured. In these cases, the test only works if the refresh is performed after rendering.


Is this expected behavior?



Best regards,

Marco.





VN Vignesh Natarajan Syncfusion Team May 2, 2022 11:58 AM UTC

Hi Marco,


Thanks for the update. 


Query: “In these cases, the test only works if the refresh is performed after rendering. Is this expected behavior?


Yes, because some complex properties of GridColumn require some additional refresh to run the Unit test case without error. We have ensured the GridComponent with different combinations, and we have faced some issues with test cases. Hence we added an additional refresh in the OnAfterRenderedAsync to succeed in the test case.


Please get back to us if you have further queries or if you are facing issues with it.


Regards,

Vignesh Natarajan



Loader.
Up arrow icon