Comments (20)
Im thinking something slightly different.
Suppose we have a test where the user wants to stub/mock out the component, e.g.:
[Fact]
public void MyTest()
{
ComponentFactories.AddStub<TargetComponent>();
// ...
}
That is enough to signal that we should generate a stub/mock type for TargetComponent
. We can use a source generator to pick up all calls to AddStub
, pick out the TComponent
, and generate a partial TargetComponentStub
. That way users don't have to create partial class unless they have special needs.
If users want to customize the stub, they can create a public partial class TargetComponentStub { ... }
in their test project.
But, I think it is an opportunity to to create more than a stub. We could create a mock instead, where the user is able to configure the behavior and provide markup to be rendered.
But then, what about if users want to mock out all components from a namespace? Should we generate a whole bunch of mock types, which could be costly. That said, that would only happen once, or, when the user changes dependencies.
Another scenario is when users want to mock/stub their own types to isolate a component under test. Then the mockee may be changed pretty often and thus that should also influence the mock.
from bunit.
I will push my alpha version of the source generator as described initially.
The reason I don't want to use Stub is that I want to give the user the ability to partially override properties.
That wouldn't work with the approach you described.
from bunit.
Would be interesting to pull in @scottsauber and ask how often, if ever, he uses mocks/stubs of components, and if he has any experience relevant to this.
from bunit.
I create a draft PR, including the first tests - there are MANY open questions. If you like I can annotate them to open up a discussion.
from bunit.
Would be interesting to pull in @scottsauber and ask how often, if ever, he uses mocks/stubs of components, and if he has any experience relevant to this.
Almost never. The React community used to do shallow rendering like this with the library Enzyme, but went away from it in favor of React Testing Library and deep rendering.
Basically using the guidance of “the more your tests resemble the way your users use your application, the more confidence they give you.”
More reading on that here - https://kentcdodds.com/blog/why-i-never-use-shallow-rendering
I will say it is annoying though when certain component libraries heavily use JS (ie Radzen does for its datepicker) instead of using pure C#. So that is a slight difference between bUnit and RTL, where RTL can run all code bc it’s all JS and bUnit can’t run any JS.
from bunit.
The reason I don't want to use Stub is that I want to give the user the ability to partially override properties. That wouldn't work with the approach you described.
Yes it would. And it does not require users to create a partial stub class, unless they have special needs. Most probably don't have that need, so it would lead to a whole bunch of empty partial classes just to create a stub.
Generate the default stub with the default implementation with a predictable/conventional name. Then, if users do create a partial class with the same name, that partial class is taken into consideration. If they create a partial class with a different name WITH an Stub
attribute, then that is name that is used.
from bunit.
I will say it is annoying though when certain component libraries heavily use JS (ie Radzen does for its datepicker) instead of using pure C#. So that is a slight difference between bUnit and RTL, where RTL can run all code bc it’s all JS and bUnit can’t run any JS.
@scottsauber, thanks for jumping in. Yes, this is the motivation for this functionality. Being able to remove 3rd party components that are causing problems and making it hard to test components in isolation.
from bunit.
Yes it would. And it does not require users to create a partial stub class, unless they have special needs. Most probably don't have that need, so it would lead to a whole bunch of empty partial classes just to create a stub.
But isn't that scenario covered even today? Just call AddStub
. So if you have no need, there will be still no need even with a generator.
So the goal, from my point of view, is to unify those two worlds, given the users more control with less conventions.
from bunit.
Yes it would. And it does not require users to create a partial stub class, unless they have special needs. Most probably don't have that need, so it would lead to a whole bunch of empty partial classes just to create a stub.
But isn't that scenario covered even today? Just call
AddStub
. So if you have no need, there will be still no need even with a generator.So the goal, from my point of view, is to unify those two worlds, given the users more control with less conventions.
The problem with the Stub type is that is generic. There is a parameters dictionary on it that captures all parameters, but they are captured as object
, but it is a bit convoluted to work with if you want to assert that specific parameters were passed to it.
With a generated stub/spy/mock, we could generate:
- a class with the exact same parameters
- detect if it is safe to inherit from the stubbed component and do so for the cases where users are using the
@ref
binding - provide ways to easily assert on the parameters passed to the component
- provide a way to render out custom markup
For example, if our CUT uses <MyButton>
(the component from your issue description) like this:
<p>Hey click my button: </p>
<MyButton Text="Go ahead click me" OnClick="() => clicked =true" />
<p>@clicked</p>
@code {
private bool clicked;
}
Then the generated mock could have a RenderTreeBuilder that results in the CUT producing the following markup:
<p>Hey click my button: </p>
<MyButton Text="Go ahead click me" OnClick="() => clicked =true" />
<p>false</p>
That is, the test double would more or less be able to generate the original razor markup in the component that declared it (not sure if we are able to capture lambdas passed to e.g. OnClick
).
from bunit.
We need an externally visible package for the Source Generator that is part of the bUnit family. That said, the Attribute and generator have to be part of said package,
Agreed.
If the user already implemented a (Cascading)Parameter, we shall skip the generation
If you mean that a user creates a partial test double, we only implement the parameters the user has not implemented, then yes, I agree.
We should respect the visibility of the class provided by the user (so if it is internal - we generate an internal one as well)
I think we should generate them as internal to the test project, independent of what the visibility is of the original component.
We only want Parameter or CascadingParameter for the start. .NET 8 introduced new ones like
SupplyParameterFromForm
, which we can add later on. Or shall we add them from the get-go?
The SupplyParameterFromForm
and SupplyParameterFromQuery
works as normal parameters in bUnit, since we don't have a HTTPContext with a HTTP request body or query parameter we map from. So not sure there is a need at all to support these parameters. Probably just create them as normal parameters.
v2 only?
I am not sure there are any specific things here that need V2. We can easily support this in V1 as well. But probably an optional package that's not included by default initially, like with bunit.web.query.
Should we make sanity checks for the implementing type? For starters I would always generate a stub that derives from ComponentBase.
What kinds of sanity checks are you thinking of?
Deriving from ComponentBase
may not add any value. Do we expect our stub to have a need for the life cycle methods? It probably just need to be able to receive new parameters whenever the parent/user component decides to pass some to it.
from bunit.
I am not sure how AddStub<ThirdPartyComponent>
is enough - sure we can create a new ThirdPartyComponentStub
- but that doesn't register it in the ComponentFactory - and that is something we can't really do with Source generators.
With the Attribute
- one can use it easily with AddStub<MyComp, MyCompStub>()
.
If you mean that a user creates a partial test double, we only implement the parameters the user has not implemented, then yes, I agree.
Yes, exactly.
Deriving from ComponentBase may not add any value.
Well, no need to implement anything. And we get SetParametersAsync
for free.
I think we should generate them as internal to the test project, independent of what the visibility is of the original component.
Well if it is partial - it has to have the same visibility. But if not given - yes internal
is absolutely enough.
from bunit.
detect if it is safe to inherit from the stubbed component and do so for the cases where users are using the @ref binding
That is a huge task! Given that we might into so many edge cases. I also don't feel that there is much benefit in even mimicking the markup - that might change with every version of a library, leaving the user at a place where he started.
from bunit.
I am not sure how AddStub is enough - sure we can create a new ThirdPartyComponentStub - but that doesn't register it in the ComponentFactory - and that is something we can't really do with Source generators.
Interceptors in .net 8 allows us to do this.
But yeah, a different method none the less may be a good idea.
That is a huge task! Given that we might into so many edge cases. I also don't feel that there is much benefit in even mimicking the markup - that might change with every version of a library, leaving the user at a place where he started.
Yeah, without a doubt. Let's figure out what a MVP would be and do that. Then we can always add more on top of that.
I'm as always very inspired and ambitious 🙂
from bunit.
Just realized there is an annoying limitation with source generators. They cannot work on types created by other source generators in the target assembly. The Blazor compiler is a source generator. That means supporting this .razor test files is going to be a problem, as far as I can tell.
from bunit.
I do have a working version. Another limitation is that the user has to add:
<InterceptorsPreviewNamespaces>$(InterceptorsPreviewNamespaces);MyFancyNamespace</InterceptorsPreviewNamespaces>
To their csproj file.
Anyway - I made it work with interceptors. There are many rough edges. I pushed it to the given branch
from bunit.
Big issues at the moment:
Calling AddStubGenerated
multiple times leads to duplicates(stubbing the same component in different tests), and I am not sure if there are ways around that (besides ugly random numbers at the interceptors class name), as generators can't see the output of other generators, as you said.
That said - the attribute version is the most reliable one at the moment from my point of view.
from bunit.
Big issues at the moment:
Calling
AddStubGenerated
multiple times leads to duplicates(stubbing the same component in different tests), and I am not sure if there are ways around that (besides ugly random numbers at the interceptors class name), as generators can't see the output of other generators, as you said.That said - the attribute version is the most reliable one at the moment from my point of view.
Keep track of generated types in a hashset?
from bunit.
Got it working with multiple - I needed a better understanding of the Generator API.
from bunit.
In v1
of the generator, I would like to concentrate on the 80% use case - that is, having no partial stub around that we have to take care of.
With that I am pretty far and have to patch out some rough edges.
from bunit.
I also updated the description to reflect the latest discussions
from bunit.
Related Issues (20)
- Improve interacting with substituted components (Stub<T>) HOT 1
- Multi-threaded test execution appears to cause a race condition when using SetParametersAndRender HOT 21
- Showcase NUnits `LifeCycle.InstancePerTestCase` to come around the wrapper HOT 1
- Enable documentation for `v1` and `v2` HOT 2
- Failing tests on ubuntu-latest github actions HOT 14
- StackOverflow Exception when running tests with vstest.console.exe HOT 10
- TestRender.DisposeComponents() does not correctly dispose components that implement IAsyncDisposable HOT 6
- Add Support for `@onclose` and `@oncancel` events for `<dialog>`
- `System.MissingMethodException` after update to .NET 8 RC 2 HOT 17
- 1.24.10 unit tests hang randomly when run in parallel HOT 13
- Find fails with System.TypeLoadException: Method 'get_GivenNamespaceUri' from assembly 'AngleSharpWrappers does not have an implementation HOT 2
- Issues with Fluxor in version 1.24.10 HOT 4
- I got a execption in .NET 8: Method not found: 'System.String Microsoft.AspNetCore.Components.CascadingParameterAttributeBase.get_Name()'. HOT 1
- bUnit fails with System.InvalidOperationException: The render handle is not yet assigned. HOT 5
- `Render(RenderFragment)` doesn't re-render and has other limitations HOT 9
- `IRenderedComponent.InvokeAsync` should support return values HOT 8
- 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
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.