bUnit: Change Uri in NavigationManager from async method in component is not always visible in test

Describe the bug

I have a OnValidSubmit which triggers the HandleValidSubmit method. This updates my data and afterwards triggers my navigationmanager to redirect. When I test it within browser redirection works fine but in my test it returns navMan.Uri => "http://localhost/"

Example: Testing this component:

@page "/Angebot-erstellen"

<div class="container">
    <div class="row">
        <div class="col-md-12">
            <div class="card shadow mb-3">
                <div class="card-header py-3">
                    <h4 class="m-0 font-weight-bold">Angebot erstellen</h4>
                                <EditForm Model="@_currProposal" OnValidSubmit="@HandleValidSubmit">
                                        <label class="h4 mt-2">Titel</label><InputText @bind-Value="_currProposal.Title" class="form-control" type="text" required="" minlength="3"/><label class="h4 mt-2">Beschreibung</label><InputTextArea @bind-Value="_currProposal.Description" class="form-control" style="margin-top: 11px;" required="" minlength="10" rows="5"/><label class="h4 mt-2">Branche</label>
                                        <InputSelect @bind-Value="_currProposal.Field" class="form-control form-control-sm custom-select custom-select-sm h5">
                                            <option value="0" selected="">Bitte Branche auswählen...</option>
                                            <option value="Architektur/Bau/Immobilien">Architektur/Bau/Immobilien</option>
                                            <option value="Automobil">Automobil</option>
                                            <option value="Banken/Finanzsektor/Versicherung">Banken/Finanzsektor/Versicherung</option>
                                            <option value="Beratung">Beratung</option>
                                            <option value="Bildung">Bildung</option>
                                            <option value="Chemie">Chemie</option>
                                            <option value="Dienstleistung">Dienstleistung</option>
                                            <option value="Druck/Verpackung">Druck/Verpackung</option>
                                            <option value="IT">IT</option>
                                            <option value="Elektro/Elektronik">Elektro/Elektronik</option>
                                            <option value="Energie">Energie</option>
                                            <option value="Forschung/Entwicklung">Forschung/Entwicklung</option>
                                            <option value="Gesundheit/Soziales/Pflege">Gesundheit/Soziales/Pflege</option>
                                            <option value="Kunst/Kultur">Kunst/Kultur</option>
                                            <option value="Landwirtschaft/Nahrungsmittel">Landwirtschaft/Nahrungsmittel</option>
                                            <option value="Logistik/Transport/Verkehr">Logistik/Transport/Verkehr</option>
                                            <option value="Luft- und Raumfahrt">Luft- und Raumfahrt</option>
                                            <option value="Marketing/Werbung/PR">Marketing/Werbung/PR</option>
                                            <option value="Maschinen- und Anlagenbau">Maschinen- und Anlagenbau</option>
                                            <option value="Medizin/Pharma">Medizin/Pharma</option>
                                            <option value="Medizintechnik">Medizintechnik</option>
                                            <option value="Optik">Optik</option>
                                            <option value="Robotik">Robotik</option>
                                            <option value="Sport">Sport</option>
                                            <option value="Steuerberatung/Wirtschaftsprüfung">Steuerberatung/Wirtschaftsprüfung</option>
                                            <option value="Tourismus">Tourismus</option>
                                            <option value="Gastronomie">Gastronomie</option>
                                        </InputSelect>
                                        <div class="p-1 text-right mb-0">
                                            <button style="background: #61a48c;color: rgb(255,255,255);" type="submit" >Next<i class="oi oi-arrow-right"></i></button>
                                        </div>
                                    </EditForm>
                </div>
        </div>
    </div>
</div></div>

using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components;
using Team01.Models;
using Team01.Services.Interfaces;


namespace Team01.Pages
{

    public partial class CreateProposal
    {
        private Proposal _currProposal;
        [Inject] private IProposalService ProposalService { get; set; }
        [Inject] private NavigationManager NavigationManager { get; set; }

        protected override async Task OnInitializedAsync()
        {
            _currProposal = new Proposal()
            {
                DateCreated = DateTime.Now,
            };
        }


        private async Task HandleValidSubmit()
        {
            await ProposalService.UpdateProposalAsync(_currProposal);
            NavigationManager.NavigateTo("Angebot-erstellen/skills");
        }

    }
}

With this test:

  [Test]
        public async Task CreateProposal_SuccsessfullyNavigatesToSkillPage()
        {
            using var ctx = new Bunit.TestContext();
            ctx.Services.AddSingleton<IProposalService>(_proposalService);
            var navMan = ctx.Services.GetRequiredService<NavigationManager>();

            var cut = ctx.RenderComponent<CreateProposal>();
            
            cut.Find("input").Change("TestTitle");
            cut.Find("textarea").Change("A proposal description");
            cut.Find("select").Change("Beratung");
            cut.Find("form").Submit();
           
            
            Assert.AreEqual($"{navMan.BaseUri}Angebot-erstellen/skills", navMan.Uri);
 
        }

Results in this output:

  Expected string length 41 but was 17. Strings differ at index 17.

Expected behavior: it should go to the desired link like it does within in the browser.

Version info:

  • bUnit version: 1.1.5
  • .NET Runtime and Blazor version: .NET5.0
  • OS type and version: macOS Big Sur 11.4

Additional context: Once I add an onclick to the button and trigger the nav manager here it works as expected.

within test:

            cut.Find("form").Submit();
            cut.Find("button").Click();

            Assert.AreEqual($"{navMan.BaseUri}Angebot-erstellen/skills", navMan.Uri);

and html:

<button style="background: #61a48c;color: rgb(255,255,255);" @onclick=@(() => NavigationManager.NavigateTo("/Angebot-erstellen/skills")) type="submit" >Next<i class="oi oi-arrow-right"></i></button>

About this issue

  • Original URL
  • State: closed
  • Created 3 years ago
  • Comments: 18 (11 by maintainers)

Commits related to this issue

Most upvoted comments

@egil I just tried, everything looks good so far. thanks for coming up with new solutions 😃

I have to change it like this so it is working:

 public async Task UpdateProposalAsync(Proposal proposal)
        {
            await Task.Delay(1);
        }

and changed the method to:

 private async Task HandleValidSubmit()
        {
            await ProposalService.UpdateProposalAsync(_currProposal);
            NavigationManager.NavigateTo("Angebot-erstellen/skills");
 
        }

When I run the test now it is basically the same erorr:

Expected string length 41 but was 17. Strings differ at index 17.

I will try to do it though got a lot going on due to exams.

No worries, appreciate the help, since these issues can be hard to replicate on other machines with different processors/OS’s.

could I simplify my code for this purpose as well?

Yes. In essens, replace the content of your UpdateProposalAsync(Proposal proposal) with return Task.Delay(1) and see if it still has the problem.

OK, then it might be a synchronization issue between different CPU cores. When we have components doing async work, its runs in a different thread than the test runs in. If those two threads runs on different CPU cores on your computer, then the updated URL string might get stuck in the cache of one cpu core and not be visible on the other CPU.

It will take some experimentation to figure out how to trigger that sync. Usually a lock statement around the Uri property will help. https://source.dot.net/#Microsoft.AspNetCore.Components/NavigationManager.cs is the real implementation which we might need to copy more from instead of the more simple fake there is right now.

That’s a bit weird. Can you try changing the HandleValidSubmit to look like this:

private Task HandleValidSubmit()
{
    NavigationManager.NavigateTo("Angebot-erstellen/skills");
    return ProposalService.UpdateProposalAsync(_currProposal);
}

Another thing you can look at, assuming ProposalService.UpdateProposalAsync takes more than one second, is to increase the timeout:

cut.WaitForAssertion(() => Assert.AreEqual($"{navMan.BaseUri}Angebot-erstellen/skills", navMan.Uri), TimeSpan.FromSecond(5));

You have already confirmed that the navigation manager correctly updated the url when its called, so there is something going on specifically in HandleValidSubmit that misbehaves.