question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Remove unneccessary abstraction

See original GitHub issue

Currently we have the following type hierarchy:

classDiagram
    IRenderedFragmentBase <|-- IRenderedComponentBase
    IRenderedFragmentBase <|-- IRenderedFragment
    IRenderedComponentBase <|-- IRenderedComponent
    IRenderedFragment <|-- RenderedFragment
    RenderedFragment <|-- RenderedComponent
    IRenderedComponent <|-- RenderedComponent

note for IRenderedComponent "Has no own methods.\nVirtually the same as IRenderedComponentBase"

Let’s subtract the nonpublic implementation classes for now - that leaves us still with the 4 interfaces we expose. With #1014 I started to remove one of them (the easy one).

History

The split was to anticipate things like Blazor mobile binding so that we have core types and types that are specific for the specific scenario - like mobile bindings or web.

Usage

How are those types currently used? Basically, we are offering those two types to the user depending on what he is calling

  • IRenderComponent<TComponent> when we call things like RenderComponent so that a user has a strongly typed object that wraps his component under test
  • IRenderedFragment that allows wrapping an “arbitrary” RenderFragment that doesn’t necessarily bag a “pre-defined” user component

There are two options we can go from here:

  1. Leave those two basic types - as they deem a good separator between those two worlds
  2. Morph them into something like IRenderedComponent<TComponent?> where TComponent : IComponent (Please note that TComponent? obviously isn’t a real constraint it is more about showing that the Instance behind that TComponent might be null)

Personally, I am leaning towards the second case where we abolish literally everything and have one base type representing a component under test, independent of its content.

For that to work, all interfaces have to be merged towards bunit.core. I am leaning out of the window and also questioning whether or not we need bunit.core and bunit.web. I do see the benefit for 3rd party maintainers to built upon bunit.core but I don’t have any feeling if people really do that. (Besides they are still able even if we would merge).

Issue Analytics

  • State:open
  • Created 7 months ago
  • Comments:7 (4 by maintainers)

github_iconTop GitHub Comments

1reaction
egilcommented, Mar 22, 2023
  1. I think the initial step for v2 is to just move all files from core and web into bUnit and remove the web and core projects.
  2. Also combine the relevant test projects.
  3. The extension methods were in some cases there because of the separation of projects (core, web).
  4. When combining the two projects, some of the design decisions becomes redundant, so there will be a need to go through the code and probably clean up and rethink some things.

My biggest concern is that this is a major task, and we probably need to break it down into small pieces that can be done in a few hours, and new refactorings can be added to a todo list. Otherwise, it will end up being a thing that never gets completed.

1reaction
egilcommented, Mar 6, 2023

Good summary. Thoughts:

  1. I know of at least a few that does depend on bUnit.core. Doing so does simplify their dependency graph because it does not include AngleSharp and friends that bUnit.web has a dependency on, that’s a big plus, as far as I can tell, since bUnit.core only depends on Microsoft framework packages. IRenderedFragmentBase does not include the Nodes property, which would require a reference to AngleSharp. That said, perhaps mot of those that take this dependency are only depending on TestContextBase.

  2. I am debating whether there is value in having an abstraction or just a base class, e.g. should we have an IRenderedFragment or a RenderedFragment. Perhaps IRenderedFragment keeps existing in bunit.core and RenderedFragment with a pointer to the DOM tree is what will exist in bUnit.web.

  3. Since I only want to keep the Render methods around (and not the generic RenderComponent methods), always have the Render methods return a RenderedFragment. We do want folks to primarily focus on testing the externally visible things, e.g. the rendered markup, and not focus on the component instance itself, so I think it’s okay to do. And we will keep the FindComponent/FindComponents methods around, which should return RenderedComponent.

At the end of the day, whenever a user calls Render, even if they are using the Render<TComponent>(parameter builder) variant, it is a RenderFragment that is being rendered, wrapped inside a BunitRootComponent, so the instance of a RenderedFragment returned from a Render() call will always point to the BunitRootComponent.

public class RenderedFragment
{
  private IEnumerable<RenderedFragment> children;
  private IComponent instance;

  // ... other properties
  
  // never liked the name `Nodes` but perhaps one of those things that we just have to stick with now
  public IEnumerable<INode> Nodes { get; }
  
  public string RawMarkup => Markup.ToHtml();

  public RenderedComponent<TComponent>? FindComponent<TComponent>() 
    => // do depth/breath first search through children;

  public IEnumerable<RenderedComponent<TComponent>> FindComponents<TComponent>() 
    => // do depth/breath first search through children;

  public IElement? Find(string cssSelector) 
    => Markup.QuerySelector(cssSelector);

  public IElement? FindAll(string cssSelector) 
    => Markup.QuerySelectorAll(cssSelector);
}

public RenderedComponent<TComponent> : RenderedFragment
{
  public TComponent Instance { get; }
}
Read more comments on GitHub >

github_iconTop Results From Across the Web

Unnecessary Abstraction!. This bad smell is caused when…
Abstract entities should have a single and important responsibility. If the creation is not necessary or just for convenience, they assume little responsibility ......
Read more >
Abstraction, remove the unnecessary to communicate
Abstraction — remove the unnecessary to communicate or visualize a concept. Sometimes when a designer knows too much it stifles new ideas.
Read more >
Avoiding Premature Software Abstractions
How we removed 80% of our code by avoiding premature software abstractions - improving development speed and reducing errors.
Read more >
programming practices - Can too much abstraction be bad?
When abstractions fail, the more of them you have layered in between the code you wrote and what's actually going on, the harder...
Read more >
How to Avoid Excessive Software Abstractions
If removing the abstraction would create excessive proliferation of duplicate code or couple components together in an undesirable way, or even ...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found