-
-
Notifications
You must be signed in to change notification settings - Fork 109
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Support for adding component factories/doubles to test context #388
Comments
DummiesA dummy component is a component that simply replaces another component in the render tree, but does nothing else besides that, i.e. no rendered output of any kind. Minimum requirements include:
This implementation does the above. public class Dummy<TComponent> : ComponentBase
{
[Parameter(CaptureUnmatchedValues = true)]
public IReadOnlyDictionary<string, object> Parameters { get; set; } = default!;
} We need an extension method that makes it easy to set up a replacement of a component with the dummy. My current suggestion is |
I think Dummy is the correct term to use for the extensions method. It makes sense to use Another thing that might be useful to add could be to be able to use a base class or interface if you have a group of components that you don't want to test in the current |
Yeah, I am not sure about the name either.
That should already be possible with the base functionality made available with the |
Cool. I can imagine using that. 👍 |
Thinking about simply calling these things public interface IComponentFactory
{
bool CanCreate(Type componentType);
IComponent Create(Type componentType);
} And an example factory that creates a public class ComponentDummyFactory : IComponentFactory
{
private readonly Type componentToDouble;
public ComponentDummyFactory(Type componentToDouble)
{
this.componentToDouble = componentToDouble;
}
public bool CanCreate(Type componentType)
=> componentType == componentToDouble;
public IComponent Create(Type componentType)
=> (IComponent)Activator.CreateInstance(typeof(Dummy<>).MakeGenericType(componentType))!;
}
public static class ComponentFactoriesExtensions
{
public static ICollection<IComponentFactory> AddDummyFactoryFor<TComponent>(this ICollection<IComponentFactory> factories)
{
factories.Add(new ComponentDummyFactory(typeof(TComponent));
return factories;
}
} And here is it used in a test: using var ctx = new TestContext();
ctx.ComponentFactories.AddDummyFactoryFor<MyChildComponent>();
var cut = ctx.RenderComponent<MyParentComponent>();
Assert.Empty(cut.FindComponents<MyChildComponent>());
Assert.NotEmpty(cut.FindComponents<Dummy<MyChildComponent>>()); |
This looks great. Conveys that this will create dummies using the "factory" for this specific type.
This also opens up for potentially creating other types of factories in the future. Like a factory for a generic mocked component or something like that. |
Another alternative is to use the name Then the vocabulary could be e.g.: ctx.ComponentCreationRules.AddDummyFor<TComponent>()
... or perhaps
ctx.ComponentCreationRules.ReplaceWithDommy<TComponent>() It's pretty wordy thought, but makes it very obvious what is going on. |
Or maybe ctx.ComponentCreationRules.UseDummyFor<TComponent>() Which also has wording like a rule: "For this type use dummy" |
ComponentFactories is good. Also consider ComponentFactories.Register() And also overrides that accept a Func<T, IComponent> parameter |
Thanks for the input Peter.
I think the convention in .net is to use Add for collection like types, or?
Can you elaborate on how this would work? |
I'm just thinking that if you had a base factory built in that just returned a stub IComponent then you could use ComponentFactories.Stub() The Register suggestion was along a similar line, but where it would return what the coder specified rather than a stubbd IComponent - the purpose here being that we don't have to write a factory for each component type we want in there. ComponentFactories.Stub(); => Mocked IComponent that does nothing |
@mrpmorris, I think I get what you are saying. What I am thinking currently is having base functionality added to So something like this will be added to bUnit.core: public interface IComponentFactory
{
bool CanCreate(Type componentType);
IComponent Create(Type componentType);
}
public interface IComponentFactoryCollection : IList<IComponentFactory> { } Then this property will show up in IComponentFactoryCollection ComponentFactories { get; } And then we and others can create extensions methods on I am leaning to wards calling the new property in The naming of the extension methods that build on this functionality I am still not sure of. In theory, these extension methods could be on using var ctx = new TestContext();
ctx.UseStubFor<TComponent>();
// ... |
It's seems the current documentation is not updated, this feature is a must to have,thank you all for all the effort you've made. unfortunately I don't find a way to use it. |
Hey @ChristopheDEBOVE, Thank you, and thanks for your support! The feature is in the preview release of bUnit you can find on nuget.org. I'm going to document it soon after releasing it/going out in a non preview package. There might still be done changes to the feature coming based on feedback, so that's why I'm holding off on the docs for now. |
@egil Thank you I found it. very valuable feature !!! |
Btw. Here is an example for using the built-in component factories in bUnit: #450 (comment) |
Component doubles make it possible to replace an irrelevant component in the render tree with another component during testing.
The proposal is to add the following to
TestContextBase
:The
ComponentDoubleCollection
is a collection ofIComponentDoubleFactory
types, which knows what type they can map from and to, and can create the double.The idea is to keep the
ComponentDoubleCollection
simple, its basically anICollection<IComponentDoubleFactory>
, and instead offer extension methods which extends and simplifies using the functionality, e.g.AddStub<TComponentToReplace>()
.This is how I current think the
IComponentDoubleFactory
could look:Example usage
To replace a component, we need something to replace it with. For the very simple case, where we simply want to replace a component with a dummy that does nothing, we can do the following:
Here is an simple implementation of a dummy component,
Dummy<TComponent>
:This can be used by a "dummy factory" like this:
Then, in a test, we can do something like this to replace
MyChildComponent
insideMyParentComponent
in a test:Obviously, the line
ctx.ComponentDoubles.Add(new DirectComponentDoubleTypeFactory(typeof(MyChildComponent));
can be simplified by an extension method to something likectx.ComponentDoubles.AddDummyFor<MyChildComponent>();
.Naming
The term "component doubles" comes from the term "test doubles", which is a general term for things like mocks, fakes, stubs.
I am still not sure about the word "double" though. Perhaps the word "replace"/"replacement" is better?
Requirements
The solutions should allow these, which implicitly closes #53 and enables #17 to be closed, with some limitations on concurrent renders.
All of these requirements seems to be fairly trivial to support with the right
IComponentDoubleFactory
added to theComponentDoubles
collection onTestContext
.The next question is what/which
IComponentDoubleFactory
's should come with bUnit out of the box.Questions and investigations
@ref="childComponent"
?Implementation details
This utilizes the
IComponentActivator
in .NET 5 and later versions of Blazor. The idea is to have aBunitComponentActivator
that knows about theIComponentDoubleFactory
added to theComponentDoubleCollection
and will run through those and try to find the first one that can create a double for a component. This should happen in reverse order, so it tries the last added activator first. If not, it will just create the requested component.This is the current basic implementation of the
BunitComponentActivator
, which is passed to the renderer, and is used to instantiate components:The text was updated successfully, but these errors were encountered: