Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Test-Driving Kotlin in ZK (Part 2)

DZone's Guide to

Test-Driving Kotlin in ZK (Part 2)

This time around, we see how to create test cases with Kotlin for your ZK apps. Check out some suggestions and limitations to be aware of.

· Java Zone ·
Free Resource

Atomist automates your software deliver experience. It's how modern teams deliver modern software.

As promised in Part 1, it's now time to build a more useful example to try out if/how data-classes work in a ZK application. Finally wrapping up to check how Kotlin might help when writing the oh-so-dreaded test cases.

Building a Simple CRUD UI

Image title

The screenshot on the left shows a basic CRUD UI (without any fancy styling, just concentrating on the essentials) for Persons (with an Address).

As the acronym indicates, it is possible to Create new and Read/Update existing Persons, as well es Deleting them.

Using ZK's-MVVM pattern should be straightforward, so this article can concentrate on how to use idiomatic Kotlin features during the implementation and how to overcome obstacles on the way.

Let's dive into the implementation.

Using Data Classes

Data classes are created in a breeze — just define the properties and their default values.

I think I don't need to paste the equivalent Java code here ... everyone knows the ceremony (generate getters, setters, constructors, equals, hashcode, toString, clone, etc). All these come for free (with reasonable defaults) when using a data-class.

data class Person(var name: String = "", var age: Int = 0, var address: Address = Address())

data class Address(var street: String = "", var houseNumber: Int = 0, var city: String = "")


Looks nice, but do they also work in combination with ZK's property-binding? (And what about the proxy-based form-binding?)

The answers:

property-binding: YES works instantly -> the implicit getters/setters are fully compatible - so one can use them in bind-annotations and EL-expressions.

<!-- dynamic bind annotations -->
<label value="@load(person.name)"/>
<intbox value="@bind(person.age)"/>
<textbox value="@bind(person.address.street)"/>

<!-- static EL -->
<label value="${person.name} (${person.age})"/>


form-binding: YES (but ... not instantly)

Naively I tried this ...

<div sclass="grid-2" 
     form="@id('fx') @load(vm.personToEdit) @save(vm.personToEdit, before=cmds.SAVE)">
    Name <textbox value="@bind(fx.name)"/>
    Age <intbox value="@bind(fx.age)"/>
    Street <textbox value="@bind(fx.address.street)"/>
    House Number <textbox value="@bind(fx.address.houseNumber)"/>
    City <textbox value="@bind(fx.address.city)"/>
</div>


... resulting in the following exception.

java.lang.RuntimeException: zk.kotlin.vm.Person is final
at javassist.util.proxy.ProxyFactory.checkClassAndSuperName(ProxyFactory.java:804)
at javassist.util.proxy.ProxyFactory.makeSortedMethodList(ProxyFactory.java:826)
at javassist.util.proxy.ProxyFactory.computeSignature(ProxyFactory.java:836)
at javassist.util.proxy.ProxyFactory.createClass(ProxyFactory.java:398)
at org.zkoss.bind.proxy.ProxyHelper.createFormProxy(ProxyHelper.java:246)
at org.zkoss.bind.impl.FormBindingImpl.initFormBean(FormBindingImpl.java:81)
...


"OK", I thought. Fair enough, classes are final by default in Kotlin (preventing Java-proxies from generating subclasses dynamically). No problem, I just declare it open instead, but then ...

Image title

... better RTF(riendly)M.

Luckily, especially for those cases, there are compiler plugins (called allopen and noarg) that generate extensible classes compatible with Java-proxies (also useful when dealing with Hibernate entity classes or Spring proxies).

The allopen plugin makes compiled classes open so they can be extended at runtime, but still keeps them final to avoid unintended programming mistakes.

In a similar way, the noarg plugin adds a no-arg-constructor, allowing runtime instantiation of generated proxy classes without exposing it to the developer unnecessarily.

We all know boilerplate code(-comments) like this, which complicate our well thought-out API - allowing for Person instances with uninitialized properties if used improperly:

    public class Person {
        //for framework X using proxies, don't call it in application code
        public Person() {}
        ...
    }


The noarg pugin solves this nicely. (I know Lombok has it too — here I am talking about Kotlin — but always good to have alternatives).

To enable the plugins just add them to build.gradle:

apply plugin: "kotlin"
apply plugin: "kotlin-noarg"
apply plugin: "kotlin-allopen"

noArg {
    annotation("zk.kotlin.vm.FormBean")
}
allOpen {
    annotation("zk.kotlin.vm.FormBean")
}


In my example, both plugins are configured to only affect classes annotated with my own @FormBean annotation (other configuration methods, e.g. package scanning, also exist).

After adding the annotations, the data-classes (Person / Address) now elegantly support form-binding.

@FormBean data class Person(var name: String = "", var age: Int = 0, var address: Address = Address())

@FormBean data class Address(var street: String = "", var houseNumber: Int = 0, var city: String = "")


Amazing! For a short moment, I thought about giving up on data-classes. This was, again, a very positive surprise on how much Kotlin cares about interoperability with existing libraries.

Without the blocking obstacles the remaining UI code is trivial, just see for yourself:

src/main/kotlin/zk/kotlin/vm/PersonCrudViewModel.kt and src/main/webapp/personCrud.zul

Other Goodies

1. Worth mentioning is the commands-object in PersonCrudViewModel. Aggregating the commands, the ViewModel wants to expose to the ZUL file (almost don't need to mention anymore: in a refactoring-safe manner).

    val commands = object {
        val CREATE = ::create.name
        val DELETE = ::delete.name
        val EDIT = ::edit.name
        val SAVE = ::save.name
        val CANCEL = ::cancel.name
    }


In the ZUL file, they can be accessed via @command(vm.commands.SAVE) or as in my example shortened by a reference binding cmds="@ref(vm.commands)" -> @command(cmds.SAVE)(personCrud.zul Line 11 / Line 45).

2. Kotlin's (setter/getter functions) can be used for calculated view model properties with dependencies.

The property personToEdit holds the current person being edited. Whenever it is changed in the code, I want the UI to update (i.e. all data-bindings referring to this instance to reload). The depending flag editing is notified programmatically to render/re-render/detach the editPersonForm accordingly.

In Java, this can be implemented like that:

    private Person personToEdit = null;

    public Person getPersonToEdit() {
        return personToEdit;
    }

    public void setPersonToEdit(Person personToEdit) {
        this.personToEdit = personToEdit;
        BindUtils.postNotifyChange(null, null, this, "editing")
        BindUtils.postNotifyChange(null, null, this, "personToEdit")
    }

    public boolean isEditing() {
        return personToEdit != null;
    }


The hard-coded Strings (mentioned in the previous article) corresponding to property names could break during refactoring without the compiler complaining.

The same in Kotlin:

    var personToEdit: Person? = null
        set(value) {
            field = value
            notifyChange(::editing)
            notifyChange(::personToEdit)
        }

    val editing get() = personToEdit != null


Now the compiler and IDE can help reliably when updating property names or trying to find out which other functions use a specific field or which field depends on another.

In order to update the UI, all you need is to assign a person: personToEdit = person.
To stop editing setting it to null: personToEdit = null.

    //new person
    @Command
    fun create() {
        personToEdit = Person()
    }

    //edit existing person
    @Command
    fun edit(@BindingParam("person") person: Person) {
        personToEdit = person
    }

    //cancel editing a person
    @Command
    fun cancel() {
        personToEdit = null
    }


In each case, the setter-function is called updating the field, and notifying the UI to reflect the changes automatically.

Testing With ZATS

ZK comes with a testing framework called ZATS, which allows testing ViewModels/Controllers by mimicking the useragent without the complexities and maintenance effort of real browser tests (e.g. using Selenium — still Selenium tests are important to verify the final result is working in each targetted browser). The goal with ZATS is to test as much interaction between ZK and your application as possible without having to deal with real browser specifics. As a benefit, the test-cases run much faster — encouraging to run them more often during development.

Simplified spoken the ZATS API serves 2 functions:

  1. Query components on a page (find and access their state).
  2. Trigger events (mimic user interaction — resulting in UI changes).

In Java, a simple test case may look like this:

@Test
public void testMyIndex() {
    DesktopAgent desktopAgent = client.connect("/myIndex.zul");

    ComponentAgent nameInput = desktopAgent.query("#name");
    ComponentAgent submitButton = desktopAgent.query("#submit");
    ComponentAgent responseLabel = desktopAgent.query("#response");

    assertEquals("", nameInput.as(Textbox.class).getValue());
    nameInput.input("Tester");
    assertEquals("Tester", nameInput.as(Textbox.class).getValue());

    submitButton.click();
    assertEquals("Hello Tester!", responseLabel.as(Label.class).getValue());
}


The same in Kotlin ... Works, but looks a little ugly — maybe hard to read later on.

@Test
fun testMyIndex {
    val desktopAgent = client.connect("myIndex.zul")

    val nameInput = desktopAgent.query("#name")
    val submitButton = desktopAgent.query("#submit")
    val responseLabel = desktopAgent.query("#response")

    assertEquals("", nameInput.`as`(Textbox::class.java).value)
    nameInput.input("Tester");
    assertEquals("Tester", nameInput.`as`(Textbox::class.java).value)

    submitButton.click();
    assertEquals("Hello Tester!", responseLabel.`as`(Label::class.java).value);
}


Unfortunately — not being originally designed for Kotlin — ZATS uses a reserved word, as, as a function name so it needs to be escaped using backticks `as`. On top of that, the references to Java classes aren't beautiful either. With a simple extension function for the ComponentAgent interface, this can be streamlined using an Inline Function with Reified Type Parameters (sounds frightening at first — but is well-explained). I often miss such a feature in Java — maybe Project Valhalla will add this in future Java versions.

inline fun <reified T : Component> ComponentAgent.asComp(): T = this.`as`(T::class.java)


I am not fully certain that this is the simplest way to do this. At least after implementing this function in one place, the remaining test case becomes a lot easier to read (IMO).

@Test
fun testMyIndex() {
    val desktopAgent = client.connect("/myIndex.zul")

    val nameInput = desktopAgent.query("#name")
    val submitButton = desktopAgent.query("#submit")
    val responseLabel = desktopAgent.query("#response")

    assertEquals("", nameInput.asComp<Textbox>().value)
    nameInput.input("Tester");
    assertEquals("Tester", nameInput.asComp<Textbox>().value)

    submitButton.click();
    assertEquals("Hello Tester!", responseLabel.asComp<Label>().value)
}


To increase maintainability, the component selectors can be defined above the test functions, which makes them reusable in multiple test methods. Adjusting them in one place should be sufficient when pages are restructured or component IDs change.

private val QueryAgent.nameInput get() = query("#name")
private val QueryAgent.submitButton get() = query("#submit")
private val QueryAgent.responseLabel get() = query("#response")

private val ComponentAgent.asTextbox get() = asComp<Textbox>()
private val ComponentAgent.asLabel get() = asComp<Label>()

@Test
fun testPage() {
    with(client.connect("myIndex.zul")) {
        assertEquals("", nameInput.asTextbox.value)
        nameInput.input("Tester");
        assertEquals("Tester", nameInput.asTextbox.value)

        submitButton.click();
        assertEquals("Hello Tester!", responseLabel.asLabel.value)
    }
}


At the same time, repeated component casting and property access can be extracted into additional extension functions (here done by asTextbox/asLabel). I might be over-engineering things a little, so decide for yourself what's useful for your eyes.

Since most query operations are performed on the same desktopAgent object, Kotlin's with() {} is a match-made-in-heaven to further reduce distracting code (Line 10 in the code above).

The line nameInput.input("Tester") will actually call this.nameInput.input("Tester"), where this refers to the DesktopAgent object returned by client.connect(...).

I Want a More Complex Example

Going back the PersonCrud ... a test case for all CRUD-functions of the above example can be implemented in just under 100 lines without looking too cryptic. See for yourself: PersonCrudTest.kt.

This includes chained component selectors (e.g. to get the delete button after the 2nd person in the personList — Line 59)

personRow(2).deleteButton.click()


... or querying elements inside other elements using a nested with()

    @Test
    fun testPersonCrudEditSave() {
        with(connectPersonListPage()) { //with DesktopAgent
            personRow(2).editButton.click() //search below desktop
            assertNotNull(personEdit)
            with(personEdit) { //with ComponentAgent personEdit
                assertEquals("Graham", nameInput.asComp<Textbox>().value)
                assertEquals(37, ageInput.asComp<Intbox>().value)

                nameInput.input("Grammy") //search nameInput below personEdit
                ageInput.input(100) //search ageInput below personEdit
                saveButton.click() //search saveButton below personEdit
            }
            assertNull(personEdit) //search personEdit below desktop
            assertEquals("Grammy (100)", personRow(2).asComp<Label>().value)
        }
    }


Because personEdit is a calculated value, its result can change with each evaluation. That's the reason we can use it multiple times to assert changes between NULL and non-NULL values. We can't accidentally reuse a stale value.

In lines 5 and 14, assert[Not]Null(personEdit) will query the personEdit each time to check the editPerson element is actually removed from the page after clicking the save-button (in line 12).

Epilogue

In the end, this was an insightful experience for me and I am looking forward to future language developments be it Kotlin or Java 10, 11 ... or something different. It's great to see how much even a modern programming language such as Kotlin cares about existing APIs/Frameworks allowing for a smoother transition or, as in my case, just experimenting without having to relearn everything from scratch.

As usual, the full source code is available on github.com/zkoss-demo/zk-kotlin with running instructions.

I am looking forward to any comments or suggestions.

Get the open source Atomist Software Delivery Machine and start automating your delivery right there on your own laptop, today!

Topics:
zk framework ,kotlin ,java ,front end development ,tutorial

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}