Closed bfmsoft closed 2 years ago
Took me a little bit to figure out what was going on.
So when you are using a .razor component to run your tests from, and don't inherit from TestContext
, then the <InputeSelectEnumTest>
test component inherits by default from ComponentBase
, but since the test component is never instantiated by the renderer, but by the test runner, it doesn't have a render handler attached.
In this particular case, you created a bind-Value that captures the test component as the "receiver" of the event callback when the value changes, and the logic in Blazor sees that it implements ComponentBase
, so it expects that it should be re-rendered through a call to StateHasChanged
, but that clearly doesn't work for the <InputeSelectEnumTest>
test component.
This is something I need to document, so thanks for bringing this to my attention.
My recommended solution is to add @inherits TestContext
to the top of your test component, e.g.:
@inherits TestContext
@using BlazorApp2.Components
@using TestProject1
@using static TestProject1.TestEnums
@code {
private TestData TestData { get; set; } = new TestData();
[Fact]
public void RenderDefault() {
var renderedComponent = Render<InputSelectEnum<Country>>(
@<EditForm Model="TestData" >
<DataAnnotationsValidator />
<InputSelectEnum Id="TestCountryId"
@bind-Value="TestData.TestCountry" />
<ValidationSummary></ValidationSummary>
</EditForm>
);
renderedComponent.MarkupMatches(
@<select id="TestCountryId" class="valid" value="Default" >
<option value="Default" selected="">Default</option>
<option value="UnitedStates">United states</option>
</select>
);
TestData.TestCountry = Country.UnitedStates;
var x = renderedComponent.Find("#TestCountryId");
x.Change("UnitedStates");
renderedComponent.Render();
renderedComponent.MarkupMatches(
@<div class="form-control-wrapper">
<select id="TestCountryId" class="form-control valid" >
<option value="0">Default</option>
<option value="1" selected="">United States</option>
</select>
</div>
);
}
}
This allows you to just access all the TestContext methods directly instead of through a ctx variable, which makes the code a little simpler (I think), and there is no need to instantiate the test context and dispose if it in every test. That is handled implicitly for you by xUnit.
If you prefer not to inherit from TestContext, you have to inherit from another class. It can just be an almost empty one, like this one:
public class MyTestBase
{
protected virtual void BuildRenderTree(Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder builder) { }
}
Describe the bug
It is entirely possible I don't understand. But, I think this should work? Is there something else I need to do? I have created a small sample project that shows the issue.
Example: Please see sample project. 1) Run the one test in the test project. It will throw
With this test: See Sample
Results in this output: estProject1.RazorTestComponents.InputeSelectEnumTest.RenderDefault Source: InputeSelectEnumTest.razor line 8 Duration: 446 ms
Message: System.InvalidOperationException : The render handle is not yet assigned.
Stack Trace: RenderHandle.ThrowNotInitialized() RenderHandle.Render(RenderFragment renderFragment) ComponentBase.StateHasChanged() IHandleEvent.HandleEventAsync(EventCallbackWorkItem callback, Object arg) EventCallbackb0_0(String value) line 19
<>c__DisplayClass32_0`1.b 0(ChangeEventArgs e)
--- End of stack trace from previous location ---
ComponentBase.CallStateHasChangedOnAsyncCompletion(Task task)
Renderer.GetErrorHandledTask(Task taskToHandle, ComponentState owningComponentState)
TestRenderer.AssertNoUnhandledExceptions() line 350
TestRenderer.DispatchEventAsync(UInt64 eventHandlerId, EventFieldInfo fieldInfo, EventArgs eventArgs) line 93
TriggerEventDispatchExtensions.TriggerNonBubblingEventAsync(ITestRenderer renderer, IElement element, String eventName, EventArgs eventArgs) line 99
TriggerEventDispatchExtensions.TriggerEventAsync(IElement element, String eventName, EventArgs eventArgs) line 55
InputEventDispatchExtensions.ChangeAsync(IElement element, ChangeEventArgs eventArgs) line 42
InputEventDispatchExtensions.Change[T](IElement element, T value) line 24
InputeSelectEnumTest.RenderDefault() line 29
1.InvokeAsync(TValue arg) InputBase
1.set_CurrentValue(TValue value) InputBase1.set_CurrentValueAsString(String value) InputSelectEnum
1.Expected behavior:
Test should pass
Version info:
*Additional context: BlazorApp2.zip *