Comments (9)
I am not really surprised by this. A test component is not a real component, there is no render handler attached to it.
Did a few experiments:
@inherits TestContext
@code {
public RazorStuff(ITestOutputHelper output)
{
// uses the Meziantou.Extensions.Logging.Xunit package
Services.AddLogging(options =>
{
options.AddProvider(new XUnitLoggerProvider(output, new XUnitLoggerOptions
{
UseUtcTimestamp = true,
IncludeScopes = false,
IncludeCategory = true,
IncludeLogLevel = true,
TimestampFormat = "s"
}));
options.SetMinimumLevel(LogLevel.Trace);
});
}
[Fact]
public void ThatIsMyCoolTest()
{
var output = string.Empty;
var cut = (IRenderedComponent<IComponent>)Render(@<button @onclick=@(() => output = "Success")>@output</button>);
cut.Find("button").Click();
cut.Render();
Assert.Equal("Success", output); // passes
Assert.Equal("Success", cut.Find("button").TextContent); // fails
}
}
- The event is triggered and the
output
variable is updated. - Casting to an
IRenderedComponent
and calling Render() again doesn't cause any additional renders, it seems. - Adding a
Assert.Equal(2, cut.RenderCount);
right after the call toClick
shows that the render doesn't actually completes. It is stuck at one render.
The log output shows that the event is processed:
Standard Output:
2023-11-22T22:00:10 dbug [Microsoft.AspNetCore.Components.RenderTree.Renderer] Initializing root component 0 (Bunit.Rendering.RootComponent)
2023-11-22T22:00:10 dbug [Microsoft.AspNetCore.Components.RenderTree.Renderer] Rendering component 0 of type Bunit.Rendering.RootComponent
2023-11-22T22:00:10 dbug [Microsoft.AspNetCore.Components.RenderTree.Renderer] Initializing component 1 (Bunit.Rendering.FragmentContainer) as child of 0 (Bunit.Rendering.RootComponent)
2023-11-22T22:00:10 dbug [Microsoft.AspNetCore.Components.RenderTree.Renderer] Rendering component 1 of type Bunit.Rendering.FragmentContainer
2023-11-22T22:00:10 dbug [Bunit.Rendering.TestRenderer] Component 0 has been rendered.
2023-11-22T22:00:10 dbug [Bunit.Rendering.TestRenderer] The initial render of component 0 is completed.
2023-11-22T22:00:10 dbug [Bunit.Rendering.TestRenderer] Dispatching MouseEventArgs = {"Detail":1,"ScreenX":0,"ScreenY":0,"ClientX":0,"ClientY":0,"OffsetX":0,"OffsetY":0,"PageX":0,"PageY":0,"MovementX":0,"MovementY":0,"Button":0,"Buttons":0,"CtrlKey":false,"ShiftKey":false,"AltKey":false,"MetaKey":false,"Type":null} to "onclick" handler (id = 1) on component 0.
2023-11-22T22:00:10 dbug [Microsoft.AspNetCore.Components.RenderTree.Renderer] Handling event 1 of type 'MouseEventArgs'
This is the decompiled razor code:
[Fact]
public void ThatIsMyCoolTest_decompiled()
{
var output = string.Empty;
var cut = (IRenderedComponent<IComponent>)Render((__builder2) =>
{
__builder2.OpenElement(0, "button");
__builder2.AddAttribute(1, "onclick", EventCallback.Factory.Create<MouseEventArgs>(this, () => output = "Success"));
__builder2.AddContent(2, output);
__builder2.CloseElement();
});
cut.Find("button").Click();
cut.Render();
Assert.Equal("Success", output);
Assert.Equal("Success", cut.Find("button").TextContent);
}
from bunit.
Here are some more observations:
Render<IComponent>(@</>);
fails with an exception
That seems odd - given the fact that you can do(IRenderedComponent<IComponent>)Render(@</>);
without any problem- Re-Rendering does nothing.
var cut = (IRenderedComponent<IComponent>)Render(@</>);
cut.Render();
The problem is that the ParameterView
is now empty - therefore we never trigger the if condition in FragmentContainer
:
public Task SetParametersAsync(ParameterView parameters)
{
if (parameters.TryGetValue<RenderFragment>("ChildContent", out var childContent))
{
renderHandle.Render(childContent);
}
return Task.CompletedTask;
}
RenderFragment
doesn't change.
I guess one of the root problems of not re-rendering is, that the renderer deems theFragmentContainer
didn't change. And that makes sense. TheRenderFragment
is a delegate - that stays stable / immutable over its lifetime. That saidSetParameterAsync
will never get invoked again because of this behavior!
from bunit.
Yeah, but then again, I am not sure if this is actually a bug and even if it is something we can do something about.
The examples we are discussing here is not really scenarios I find problematic that we don't support. Do we have test scenarios that we should support but don't?
from bunit.
Well given the link from the user to SO - he wanted to test the two-way-binding of his custom component.
And I do see that as a valid scenario/approach
from bunit.
Well given the link from the user to SO - he wanted to test the two-way-binding of his custom component. And I do see that as a valid scenario/approach.
hmm, but didn't that work?
Declare a variable in the test, bind it in a component, trigger change, see variable update. The problem is/was that he also bound said variable to markup in the render fragment belonging to the test itself.
[Test]
public void TestMyInputComponent()
{
var testModel = new Person();
var editCtx = new EditContext(testModel);
var cut = Render(
@<EditForm EditContext="editCtx">
<MyCustomInputComponent Label="Firstname" @bind-Value="testModel.Firstname"></MyCustomInputComponent>
</EditForm>);
var inputElement = cut.Find("input");
inputElement.Input("John");
// Passes
Assert.That(testModel.Firstname, Is.EqualTo("John"));
}
This test verifies that two-way binding. That said, there certainly may be other reasons for needing to bind to markup declared outside components in a test. But I just can't think of a way we can do it. So I would not consider this a bug, but do think it is a good idea to note this limitation with a few examples in the docs.
My comment in the PR with the docs update came about because I didn't understand the case it was trying to explain.
from bunit.
While the test might work with the bound model - but it still might be confusing for users.
If you just put the whole code inside a razor file in your test, it "magically" works. That is at least odd behavior.
from bunit.
Yeah, I guess it could. But that's because an external razor file is a component that is managed by the rendeeer, a test written in a .razor file is not a component, and it's definitely not managed by the renderer, and I don't see a way for it to be that.
I think it would require the test to be inside a real component and that test would then have to be triggered through a custom test runner.
I am honestly happy that variables/types declared in a test can be used to test two way binding as I show above.
But still, I agree it may be surprising to some, thus I think an explainer in the docs is warranted.
from bunit.
What is more suprising is that even simple @onclick
eventhandler can lead to exceptions.
Yeah, I guess it could. But that's because an external razor file is a component that is managed by the rendeeer, a test written in a .razor file is not a component, and it's definitely not managed by the renderer, and I don't see a way for it to be that.
On your page here - just arguing from the users point of view. It is odd if you move the same code out from inside the test to a separate component - it behaves different. Furthermore, this two versions might behave different as well:
@* If we are directly inheriting from ComponentBase and create the TestContext down below - the tests from the user would pass *@
@inherits TestContext
<button @onclick"...">Click Me</button>
@code {
[Fact]
public void Title()
{
var cut = RenderComponent<ThisTestCompoinent>();
cut.Find("button").Click();
}
}
Just moving the code "up" does something else. And with v2
where everything is called Render
this might come to an even bigger surprise.
That said - I reopened #1288
from bunit.
I see the ambiguity. My point is that people should not think of razor filled with unit tests in as components. They should just use it as a .cs file with the ability to have markup mixed together with c# inside methods.
So we want to make it very clear that the template looks like this:
@inherits TestContext
@code {
...
}
And that users should think of this as classes, not components. That should remove much if the ambiguity, I think.
from bunit.
Related Issues (20)
- `IRenderedComponent.InvokeAsync` should support return values HOT 8
- Enable test doubles for (3rd party) components generation HOT 20
- Problems running docs project locally HOT 6
- Docs: search exposes pages that are not in the TOC
- bunit.web has dependency on alpha version of AngleSharp.Css HOT 2
- No way to set value for property with `[SupplyParameterFromQuery]` but not `[Parameter]` HOT 6
- Support for keyed services HOT 5
- Cannot trigger RowSelected eventcallback on SFgrid HOT 3
- `WrapperElementsGenerator` cachable
- IJSUnmarshalledRuntime removed in .NET 9 HOT 2
- FakeNavigationManager registration changed from Singleton to Scoped HOT 7
- bUnit does not set parameters as a result of two-way binding or chained binding when values are updated. HOT 6
- `@formname` attribute always causes an error: System.InvalidOperationException : Invalid element frame type 'NamedEvent' HOT 2
- upgrading bUnit to 1.27.17 results in bunit.web.testcomponents load errors HOT 2
- WASM MSAL Configuration Not Correctly Supported HOT 1
- Async Task in Multiple Tests HOT 8
- Clicking an Anchor Tag does not update NavigationManager HOT 2
- Unable to resolve service for type 'Bunit.Rendering.ITestRenderer' HOT 4
- diff:ignoreChildren does not Ignore Children of a Button HOT 1
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from bunit.