Giter Club home page Giter Club logo

Comments (20)

egil avatar egil commented on June 12, 2024

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.

linkdotnet avatar linkdotnet commented on June 12, 2024

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.

egil avatar egil commented on June 12, 2024

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.

linkdotnet avatar linkdotnet commented on June 12, 2024

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.

scottsauber avatar scottsauber commented on June 12, 2024

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.

egil avatar egil commented on June 12, 2024

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.

egil avatar egil commented on June 12, 2024

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.

linkdotnet avatar linkdotnet commented on June 12, 2024

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.

egil avatar egil commented on June 12, 2024

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.

egil avatar egil commented on June 12, 2024

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.

linkdotnet avatar linkdotnet commented on June 12, 2024

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.

linkdotnet avatar linkdotnet commented on June 12, 2024

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.

egil avatar egil commented on June 12, 2024

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.

egil avatar egil commented on June 12, 2024

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.

linkdotnet avatar linkdotnet commented on June 12, 2024

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.

linkdotnet avatar linkdotnet commented on June 12, 2024

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.

egil avatar egil commented on June 12, 2024

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.

linkdotnet avatar linkdotnet commented on June 12, 2024

Got it working with multiple - I needed a better understanding of the Generator API.

from bunit.

linkdotnet avatar linkdotnet commented on June 12, 2024

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.

linkdotnet avatar linkdotnet commented on June 12, 2024

I also updated the description to reflect the latest discussions

from bunit.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo 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.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.