0

I have an application where users have to enter different strings. Depending on the nature of the string in the application, it has to follow some rules, for example if it is a name it must begin with a letter.

I wrote a rule to be sure that a string is valid, in the case of a string that must start with a letter I wrote the following rule:

fun String.isFirstCharALetter(): Boolean = this[0].isLetter()

A function extension checks all the rules that apply to a specific string:

fun String.validateName(): String {
    check(this.isFirstCharALetter()) { "The first character of a name can only be a letter" }
    //... other checks
    return this
}

I use this function extension to instantiate a Name only if the string is valid (at least it is my intention):

data class Name(val value: String) {
    companion object {
        operator fun invoke(strg: String) {
            Name(value= strg.validate())
        }
    }
}

I wrote the following unit test:

@Test
fun test_assert_name() {
    assertFailsWith<IllegalStateException> { Name("8foo") }
}

Since 8foo does not begin with a letter, I expect that an IllegalStateException exception is raised when a Name is instantiated with this string, so the test above must succeed.

But the test fails with the following message:

java.lang.AssertionError: Expected an exception of class java.lang.IllegalStateException to be thrown, but was completed successfully.

Does anyone see where I do an error ?

5
  • 1
    When you do Name("8foo") you're not invoking the method of the companion object. You're invoking the constructor (something like new Name("8foo") in Java) Commented Jan 11, 2020 at 19:52
  • you are right @Pelocho, the constructor has to be private, see my answer Commented Jan 11, 2020 at 20:16
  • Where did this pattern of putting an income function on a companion object come from? I’ve seen it twice in the past two days. Seems like it would make code fragile, like it did here. Commented Jan 11, 2020 at 22:45
  • *invoke function Commented Jan 12, 2020 at 0:31
  • I found several examples of using invoke in a companion object (for example: kotlinlang). Why do you say that it make code fragile? Commented Jan 13, 2020 at 8:00

2 Answers 2

1

To run invoke operator you needed to call

val name = Name("8foo")
name("8foo")

Then invoke will be called with exception

Maybe you should check first letter at init{} section?

class Name(private val string:String) {

    init{
        check(false){"Error"}
    }

You also can use regex to check if string starts with letter

Sign up to request clarification or add additional context in comments.

1 Comment

Beware of regex! For a simple task like this, using a regex is totally overkill. Take a look at blog.codinghorror.com/…
0

I found the solution :)

As stated here: The invoke() operator allows instances of your classes to be called as functions. (I could not find the description of invoke in the official documentation, if anyone knows where it is...)

Taking the definition above @Илья Кузьмин is right, I can use an init block.

But if I declare the constructor of my class Name as private, I have the behaviour I want. invoke is then used when I instantiate a Name object:

data class Name private constructor(val value: String) {
    companion object {
        operator fun invoke(value: String) = Name(value= value.validate())
    }
}

After making this fix all my tests succeeds.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.