Matt Fitzmaurice
Matt Fitzmaurice

Reputation: 1416

How to check markup after I get page data in OnInitializedAsync method?

I'm new to Blazor and bUnit. I have component that renders an edit form and I get the values for the form in my OnInitializedAsync event.

I'm having trouble working out how to use cut.WaitForState() or cut.WaitForAssertion().

Here's my razor code:

@page "/{AppId:guid}/app-settings-edit"
<section class="app-settings-edit">
    <h1 class="page-title">Application Settings</h1>
    @if (InitializedComplete)
    {
        <p>Hello World</p>
        ...

And my code behind:

public partial class AppSettingsEdit
{
    protected bool InitializedComplete;

    [Parameter]
    public Guid AppId { get; set; }

    [ValidateComplexType]
    public AppSettings AppSettings { get; set; } = new AppSettings();

    [Inject]
    public IAppSettingsDataService AppSettingsDataService { get; set; }

    protected override async Task OnInitializedAsync()
    {
        AppSettings = await AppSettingsDataService.Get(AppId);
        InitializedComplete = true;
    }
    ...

And here's my Test:

    [Fact]
    public void MyFact()
    {
        Services.AddSingleton<IAppSettingsDataService, MockAppSettingsDataService>(x => new MockAppSettingsDataService(x.GetRequiredService<HttpClient>()));

        var cut = RenderComponent<AppSettingsEdit>(parameters => parameters
            .Add(p => p.AppId, Guid.Parse("55E5097B-B56A-40D7-8A02-A5B94AAAD6E1"))
        );

        Assert.NotNull(cut.Instance.AppSettingsDataService);

        cut.WaitForState(() => cut.Find("p").TextContent == "Hello World", new TimeSpan(0, 0, 5));
        cut.MarkupMatches("<p>Hello World</p>");
    }

When I debug the test, I can see the OnInitializedAsync firing, however my markup never changes to include 'Hello World' and the WaitForState() command fails.

Upvotes: 1

Views: 624

Answers (2)

Jean Baumflek
Jean Baumflek

Reputation: 1

I'm having the same issue, except my app is server-side. However, the component in question is in a library. In arrange, I created the customer object with Field = "";

var cut = ctx.RenderComponent<MyComponent>(parameters => parameters
    .Add(p => p.Customer, customer)
    );

// Act
var isValid = cut.Instance.Validate();

// Assert
Assert.False(isValid);

// now wait for test warning to show up
cut.WaitForAssertion(() => Assert.NotNull(cut.Find("p#empty-warning")));

And in my component:

        @if (IsSaving && string.IsNullOrWhiteSpace(Field))
    {
        <p id="empty-warning" style="color: red;">Field is required.</p>
    }

In the //Act step, I call Validate which sets IsSaving = true; and the Field is still "".
When I run my app, obviously the change in IsSaving prompts the if to be reevaluated and then the re-render occurs. I'm not sure how else to get it to happen in bunit, the re-evaluation and re-render. Does anyone see what is wrong in my case?

Upvotes: 0

Egil Hansen
Egil Hansen

Reputation: 15598

Are you certain that the task returned from your AppSettingsDataService.Get() method ever completes?

I would make sure that the task returned from AppSettingsDataService.Get() is already completed, otherwise you need to a way to complete the task after the component is rendered. There are many ways to do this, it all depends on how your mock is implemented.

As for your WaitFor, you can just use the WaitForAssertion method in this case, i.e.: cut.WaitForAssertion(() => cut.MarkupMatches("<p>Hello World</p>"));

A little background: The WaitFor* methods are used when the component under test is being rendered asynchronously, since the test, running in a different thread, doesn't know when that will happen.

In general, you should never need to set a custom timeout, the default is 1 second, but the WaitFor* methods will retry the assertion/predicate every time a renderer happens. Its only when the thing that triggers the rendering will take more than one second, e.g. if you are using bUnit to perform end-2-end testing and e.g. pulling data from a real web service.

Upvotes: 1

Related Questions