Skip to content

Looking for how to create unit test with c# when the component creates a canvas (BECanvasComponent) #52

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
sakvin1 opened this issue Feb 4, 2020 · 4 comments
Labels
question Further information is requested

Comments

@sakvin1
Copy link

sakvin1 commented Feb 4, 2020

Hi,
I encounter an issue when I tried to create a unit test for a component that creating a canvas. Unit Test failed with this message.

Message:
Test method TestCanvas threw exception:
System.AggregateException: One or more errors occurred. (Object reference not set to an instance of an object.) ---> System.NullReferenceException: Object reference not set to an instance of an object.
IndexComponent.OnAfterRenderAsync(Boolean firstRender) line 24

This is the line that causes an error in Index.razor.cs.

24.     Console.WriteLine("width={0}.", m.Width.ToString());

Basically, I can't create a JS mock for Blazor.Extensions.Canvas.Canvas2D. Following are the code.

In Index.razor

@page "/Index"
@inherits IndexComponent
<BECanvas Width="800" Height="800" @ref="_canvasReference"></BECanvas>

In Index.razor.cs

using Blazor.Extensions.Canvas.Canvas2D;
using Blazor.Extensions;
public class IndexComponent : ComponentBase
{
    protected BECanvasComponent _canvasReference;
    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
       Canvas2DContext context = await _canvasReference.CreateCanvas2DAsync();
       var m = await context.MeasureTextAsync("Hello Blazor");
       Console.WriteLine("width={0}.", m.Width.ToString());  // Line 24 that causes the error which m returns null in this case.
    }
}

In Test.cs

[TestMethod]
public async Task TestCanvas()
{
    var jsMock = Services.AddMockJsRuntime(JsRuntimeMockMode.Loose);
    var cut = RenderComponent<Index>();
}

Please help.

@egil egil added the question Further information is requested label Feb 4, 2020
@egil
Copy link
Member

egil commented Feb 4, 2020

Hi @sakvin1

It looks as if the m is null in your case, i.e. the call to context.MeasureTextAsync("Hello Blazor") returns null, and when you try to access m.Width you are getting the exception.

I do not know the library you are using, so I am not sure what JSRuntime calls you need to mock to make the test pass. Maybe @galvesribeiro can help here.

@sakvin1
Copy link
Author

sakvin1 commented Feb 5, 2020

Thank you @egil for your quick response,

Is there any way if I can create a context in the unit test, and then inject to the component? That would work. Since it failed to create the context when rendering the component in unit test.

Canvas2DContext context = await _canvasReference.CreateCanvas2DAsync();

@egil
Copy link
Member

egil commented Feb 5, 2020

Is there any way if I can create a context in the unit test, and then inject to the component? That would work. Since it failed to create the context when rendering the component in unit test.

Canvas2DContext context = await _canvasReference.CreateCanvas2DAsync();

@sakvin1 there are two general ways of injecting dependencies into components in Blazor, through services and through cascading values. In both cases, the easiest way is if the dependencies are represented in the components that use them as interfaces (abstractions).

Then it is easy to use a framework like Moq to mock these, and inject the mock, either through the Services.AddSingleton<ICanvas2DContext>(new Mock<ICanvas2DContext>().Object), or through a cascading value, RenderComponent<IndexComponent>(CascadingValue(new Mock<ICanvas2DContext>().Object).

The problem is that <BECanvas> is a component, and we do not have a way to mock/replace those at the moment (see related issue #53).

The best solution I can come up with is for you to create an adapter around Canvas2DContext that allows you to mock it in tests and use the real in production. That is a bit of work, but will let you test your code that uses it in isolation.

The alternative is to mock the IJsRuntime (through the builtin MockJsRuntime or Moq), and setup the expected invocations from Canvas2DContext to JS and back. The problem with that approach is that you are tying your tests to the internals of Canvas2DContext, which you probably do not want to test (the authors of Canvas2DContext has supposedly done that), since if the internals of Canvas2DContext changes, then your tests will break, even if the public API of Canvas2DContext stays the same.

@egil
Copy link
Member

egil commented Feb 11, 2020

I'll close this issue. If you still need help, just comment again.

@egil egil closed this as completed Feb 11, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question Further information is requested
Projects
None yet
Development

No branches or pull requests

2 participants