Skip to content
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

Testing components that use Telerik components #141

Closed
joonastamm opened this issue Jun 4, 2020 · 13 comments
Closed

Testing components that use Telerik components #141

joonastamm opened this issue Jun 4, 2020 · 13 comments
Labels
question Further information is requested

Comments

@joonastamm
Copy link

joonastamm commented Jun 4, 2020

I have a Blazor component (page) that uses Telerik components which I wish to test but find that the Telerik handles clicks etc differently from regular Blazor components. Let's say we have a component <SomeComponent>:

<TelerikDropDownList Data="@Data"
                        @bind-Value="SelectedValue"
                        Id="value-picker"
                        OnChange="UpdateSomething">
</TelerikDropDownList>

@code {
    [Inject]
    private ISomeService SomeService { get; set; }
    private List<Option> Data = new List<Option>()
    {
        new Option{Value = 1, Text = "Option1"},
        new Option{Value = 2, Text = "Option2"}
    };
    private int SelectedValue{get; set;}

    private async Task UpdateSomething()
    {
        var result = SomeService.SomeMethod(SelectedValue);
        // do something with result
    }

    public class Option{
        public int Value{get; set;}
        public string Text{get; set;}
    }
}

And I wish to test that when "Option2" is selected then SomeService.SomeMethod is called with the chosen value. I have set up a test:
SomeComponentTests.razor:

@using Telerik.Blazor.Components

@inherits TestComponentBase

<Fixture Test="SomeTestMethod">
	<ComponentUnderTest>
		<TelerikRootComponent>
			<SomeComponent />
		</TelerikRootComponent>
	</ComponentUnderTest>
</Fixture>

and SomeComponentTests.razor.cs:

using Bunit;
using Moq;
using System;
using System.Collections.Generic;
using Telerik.Blazor.Components;

namespace App.Tests.Pages
{
	public partial class SomeComponentTests
	{
		public void SomeTestMethod(Fixture fixture)
		{
			// Arrange
            fixture.Services.AddSingleton<ITelerikStringLocalizer, TelerikStringLocalizer>();
			fixture.Services.AddMockJsRuntime();
			var someServiceMock = new Mock<ISomeService>(MockBehavior.Loose);
            fixture.Services.AddSingleton(someServiceMock.Object);

			var cut = fixture.GetComponentUnderTest<TelerikRootComponent>();
			SelectDropDownValue(cut, 2); // Should choose 2nd option in dropdown 

			// Act
			cut.Render();

			// Assert
			someServiceMock.Verify(x => x.SomeMethod(2), Times.Once);   // 'Value' for 2nd option is 2
		}

		private void SelectDropDownValue(IRenderedComponent<TelerikRootComponent> component, int option)
		{
			component.Find("#value-picker").Click();    // Throws 'The element does not have an event handler for event 'onclick', nor any other events.'
			component.Find($".k-list.k-reset > li:nth-of-type({option})").Click();
		}

        private void SelectDropDownValue2(IRenderedComponent<TelerikRootComponent> component, int option)
		{
			component.Find("span:nth-of-type(1)  .k-input").Click();    // Throws 'The element does not have an event handler for event 'onclick', nor any other events.'
			component.Find($".k-list.k-reset > li:nth-of-type({option})").Click();
		}
	}
}

but always get the same exception on Click() method call. The css selectors seem to be correct as when running the application and calling these methods via console using jQuery (e.g. $('#value-picker').click()) everything works as expected (opens the dropdown).

How would it be possible to achieve the desired result (get the test to work)?

@mihail-vladov @EdCharbeneau

@joonastamm joonastamm added the question Further information is requested label Jun 4, 2020
@joonastamm joonastamm changed the title Testing components with Telerik components Testing components that use Telerik components Jun 4, 2020
@egil
Copy link
Member

egil commented Jun 4, 2020

Thanks for a well described question. Makes it much easier to provide help.

I do not know how the click bindings are done in Teleriks components, but It could be that his is related to issue #119, i.e. that Telerik binds click handlers to elements higher up the DOM tree, and not on the individual elements.

To verify this, inspect cut.Markup and and look for attributes with a blazor: prefix. Those are how event handlers are represented in the DOM tree.

If the dropdown you are trying to change is a select or input element, you might need to use cut.Find("#value-picker").Change(ID OF OPTION TO SELECT) instead.

@joonastamm
Copy link
Author

Thank you for the quick reply!

I inspected the markup and didn't find any attributes with blazor: prefix.
Full markup:

<span aria-disabled="false" aria-haspopup="listbox" aria-activedescendant="92ce9128-012a-4f8a-84d8-0ae0fb9185eb" role="listbox" aria-describedby="9b9734a9-5d93-46ea-9c05-826e337c39f5" class="k-widget k-dropdown k-header telerik-blazor" data-id="8df2b45d-7ac8-4fa6-9db5-2c2940194068" style=" width: 300px;" tabindex="0" aria-expanded="false">
    <span class="k-dropdown-wrap k-state-default ">
            <span class="k-input" id="9b9734a9-5d93-46ea-9c05-826e337c39f5">
            </span>
        <span class="k-select">
            <span class="k-icon k-i-arrow-60-down"></span>
        </span>
    </span>

    <select value="0" tabindex="-1" id="value-picker" style="opacity: 0; width: 0px; border: 0px; z-index: -1; position: absolute;">
    </select>
</span>

    <div class="telerik-blazor k-animation-container " data-id="9467b203-60f7-43f1-962f-069ecfb94f81" style="width: ; height:200px; z-index: 10002; ">
        <div data-id="79910c8f-fc2b-4566-93aa-1567136af670" style="height: 200px; transition-delay: 0ms; transition-duration: 300ms; display: none;" class="k-popup k-reset">
</div>
    </div>

Tryng to change the dropdown value using Change() resulted in a similar exception
The element does not have an event handler for the event 'onchange', nor any other events.

@egil
Copy link
Member

egil commented Jun 4, 2020

OK. The <select> certainly looks empty and does not have any child items, nor does it have any event handlers attached.

Just for comparison, can you copy the same markup from the browser when rendering the component there?

@joonastamm
Copy link
Author

I removed some unimportant things like navbar, <!--!--> symbols etc but see that the actual rendered markup is quite similar

<html lang="en" class="csstransforms3d">

<head>
    <!-- Telerik -->
    <link rel="stylesheet" href="/css/vendor/telerik-bootstrap-theme/bootstrap/dist/all.css">
    <script src="_content/Telerik.UI.for.Blazor/js/telerik-blazor.js" defer=""></script>


    <link rel="stylesheet" href="/css/app.min.css" id="maincss">
    
</head>

<body>
    <app>
        <div id="base-container" class="aside-toggled  ">
            <div class="wrapper">
                <section class="section-container">
                    <div class="content-wrapper">
                        <span aria-disabled="false" aria-haspopup="listbox"
                            aria-activedescendant="6e5e216c-0afb-431b-b0f1-dbb4eeec7bd1" role="listbox"
                            aria-describedby="a7d257eb-74cb-4371-918d-938e4a4b2320"
                            class="k-widget k-dropdown k-header telerik-blazor"
                            data-id="d9014b77-3e6d-4539-86f3-a499b24eb9ef" style=" width: 300px;" tabindex="0"
                            aria-expanded="false" aria-owns="ba8955ef-0015-4961-82d3-ac1e81e5d066">
                            <span class="k-dropdown-wrap k-state-default ">
                                <span class="k-input" id="a7d257eb-74cb-4371-918d-938e4a4b2320">
                                </span>
                                <span class="k-select">
                                    <span class="k-icon k-i-arrow-60-down"></span>
                                </span>
                            </span>

                            <select tabindex="-1" id="value-picker"
                                style="opacity: 0; width: 0px; border: 0px; z-index: -1; position: absolute;">
                            </select>
                        </span>
                    </div>
                </section>
            </div>
        </div>
        <div class="telerik-blazor k-animation-container " data-id="ba8955ef-0015-4961-82d3-ac1e81e5d066"
            style="height: 200px; z-index: 10002; display: none;">
            <div data-id="26902d89-ccc8-4842-959e-b35797399d87"
                style="height: 200px; transition-delay: 0ms; transition-duration: 300ms; display: none;"
                class="k-popup k-reset">
            </div>
        </div>
    </app>

    <script src="_framework/blazor.webassembly.js"></script>

    <script
        type="text/javascript">var Module; window.__wasmmodulecallback__(); delete window.__wasmmodulecallback__;</script>
    <script src="_framework/wasm/dotnet.3.2.0.js" defer=""
        integrity="sha256-mPoqx7XczFHBWk3gRNn0hc9ekG1OvkKY4XiKRY5Mj5U=" crossorigin="anonymous"></script>
</body>

</html> 

@egil
Copy link
Member

egil commented Jun 4, 2020

Interesting. Do you see items in the dropdown in the browser? In that case, it looks as if it created using JavaScript.

@joonastamm
Copy link
Author

Yes, the items are visible in the browser. So probably yeah, they are manipulated by JS.
image
I see that when I open the dropdown, an div with class k-list-scroller appears in the k-animation-container etc

@egil
Copy link
Member

egil commented Jun 4, 2020

Ok. I thought Telerik was not depending on JavaScript in their components in this way. That will make it hard to test with bUnit then. But you would also be dedicating a test to testing at least some parts or Teleriks components, which is probably a waste of time.

The alternative is to make your UpdateSomething() method internal, and make it accessible to the test project using the InternalVisibleTo attribute. Then you can invoke it from a test and verify its behavior that way.

I am also thinking about shallow render/mocking components, and that would certainly solve this issue as well, but alas, that is not support currently.

@EdCharbeneau
Copy link
Contributor

The TelerikDropDown uses an animation container to create a drop-down effect. We use this because the default HTML drop down doesn't allow for us to have templates and other enhancements we add. Animations require JS to work properly, Blazor doesn't support them at the framework level.

I don't believe we're doing what is classically referred to as DOM manipulation here. Instead it's a JavaScript interop that enables us to identify when the animation starts and stops.

@joonastamm
Copy link
Author

joonastamm commented Jun 5, 2020

Hello @EdCharbeneau and thank you for the answer.
Is this approach similar for other Telerik components as well meaning it is difficult (impossible?) to use a unit testing framework like bUnit for writing a test scenario as in the opening post (i.e. when this is selected/chosen/inputted verify that a service is called with that value)?

@egil
Copy link
Member

egil commented Jun 5, 2020

I don't believe we're doing what is classically referred to as DOM manipulation here. Instead it's a JavaScript interop that enables us to identify when the animation starts and stops.

@EdCharbeneau, I imagine there is a have a good reason for Teleriks approach. My general impression is that the Telerik components are generally following Blazor best practices. That said, from what @joonastamm has shown here, it does look like JavaScript is used to both generate the items in the drop down and listen to click events on them and the drop down, which means that bUnit have nothing to trigger.

@joonastamm if the above is true, then your best bet is to make your UpdateSomething() method internal and call it directly, e.g. cut.Instance.UpdateSomething(), and that way tigger whatever logic you want to test. Remember, the primary thing related to your test should not be to test Teleriks components, so even though it is unfortunate that you cannot test that you have provided the Telerik dropdown with the right callback method, at least you can test that the callback method performs as expected.

@EdCharbeneau
Copy link
Contributor

EdCharbeneau commented Jun 5, 2020

No worries, I'll check with engineering to see what the lifecycle is like for this one. Something may have changed since I looked at the source last.

...

Just looked myself. We don't render any HTML that I can see. However, animate, change some classes, and data attributes at runtime. Also, there are quite a bit of events added. This is likely the problem. The component and each drop-down item receive event handlers for accessibility, ex: keyboard navigation.

There are also handlers for multi-select. Which is likely to cause trouble with testing since JS isn't running and adding the handlers. muli-select requires us to check for keys and may override the single select handler.

Unfortunately Blazor doesn't support all of the events we need.

@EdCharbeneau
Copy link
Contributor

@joonastamm This is typical with components that require animation. Like Drawer, Menu, and Grid filter menus. Sorry it's making testing difficult.

@egil
Copy link
Member

egil commented Jun 5, 2020

@EdCharbeneau thanks for checking up on this. I am looking into making scenarios like these easier by finding a way to enable shallow rendering (#17) or mocking/faking certain components (#53). Until then, bUnit is certainly meeting its limits in scenarios like this.

Ill close this issue as it seems there is no easy way to hook into this directly for now.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question Further information is requested
Projects
None yet
Development

No branches or pull requests

3 participants