BUnit: How to test rendering a bound value in a razor based test

148 Views Asked by At

I'm trying to test rendering a property (Firstname) bound to a custom component.

My real structure looks like

<div>
  <EditForm EditContext=editCtx>
    <MyCustomInputComponent @bind-Value="testModel.Firstname" ...></MyCustomInputComponent>
  </EditForm>
  <!-- THIS IS WHAT I WANT TO TEST -->
  <p id="result">@testModel.Firstname</p>
</div

In my test I simulated an input, changing the value of the component and test if the bound property was changed correctly. This works.

But the changed property-value is not rendered into the <p> element.

Here I have a oversimplified example of what I'm trying to do:

@using Bunit.Rendering;
@using NUnit.Framework;

@code {
  [Test]
  public void SimpleBindingTest()
  {
    using var ctx = new Bunit.TestContext();
    var person = new PersonTestModel();
    var cut = ctx.Render(@<p>@person.Firstname</p>);

    // Here my component would change the model
    // For simplicity I just change it
    person.Firstname = "John";

    // Render the content again because I don't have anything to call "StateHasChanged"
    var renderedComponent = (IRenderedComponent<IComponent>)cut;
    renderedComponent.Render();

    var pElement = cut.Find("p");
    // Fails, because the value is still empty -> <p></p>
    pElement.MarkupMatches("<p>John</p>");
  }
}

I try to trigger a re-render, after changing the bound value, similar to the description in the docs "Triggering a render life cycle on a component".

Is what I'm trying to do possible at all? Because, as you can see, I don't even test rendering a component. It is just HTML with a bound property.

Thank you.

2

There are 2 best solutions below

0
On

@Link, thanks for your help.

I created an entry in bunit gitub repository, where I got a good explanation why my approach doesn't work.

My root component is plain HTML, not a Blazor component, therefore it doesn't take part on Blazors live-cycle functionality.

The fix is to create a wrapping component and put the content of my previous apporoach inside.

It is easier to explain it with code:

Here is the TestWrapperComponent which can be used as root component for my test.

<div>
  @this.ChildContent
</div>

@code {
  [Parameter]
  public RenderFragment ChildContent { get; set; } = null!;

  public void Refresh()
  {
    this.StateHasChanged();
  }
}

And the test looks like this:

@using Bunit.Rendering;
@using NUnit.Framework;
@using BlazorDemo.Model;
@using BlazorDemo.Components;
@inherits Bunit.TestContext;

@code {
  [Test]
  public void SimpleBindingTest()
  {
    using var ctx = new Bunit.TestContext();
    var person = new Person();
    var cut = ctx.Render(
      @<TestWrapperComponent>
        <p>@person.Firstname</p>
      </TestWrapperComponent>
    );

    // Here my component would change the model
    // For simplicity I just change it
    person.Firstname = "John";

    // Get the component and call a method that invokes "StateHasChanged".
    var renderedComponent = (IRenderedComponent<IComponent>)cut;
    var wrapperComponent = renderedComponent.FindComponent<TestWrapperComponent>();
    wrapperComponent.InvokeAsync(() => wrapperComponent.Instance.Refresh());

    var pElement = cut.Find("p");
    // Passes
    pElement.MarkupMatches("<p>John</p>");
  }
}
0
On

The problem with your oversimplified version (ctx.Render(@<p>@person.Firstname</p>);) is that you are not "passing down" parameters. So, once you change person.Firstname, that RenderFragment has no concept of Parameters and therefore "doesn't see the change".

If we go away from the oversimplification and back to a real-world test: You would either invoke something inside your MyCustomInputComponent like a input-change, button click (cut.FindComponent<MyCustomInputComponent>().Find("button").Click()) to trigger a new render cycle, or, if you change the bound variable inside your test, yes you need to re-render the component under test.

So it is possible: Yes. Without a minimal reproducible example, it is hard to help out here.

If you want, you can head over to our repository: https://github.com/bUnit-dev/bUnit/discussions