LY Corporation Tech Blog

We are promoting the technology and development culture that supports the services of LY Corporation and LY Corporation Group (LINE Plus, LINE Taiwan and LINE Vietnam).

This post is also available in the following languages. Japanese

Improving code quality - Session 68: Double-edged tests

The original article was published on April 24, 2025.

Hello, I'm Munetoshi Ishikawa, a mobile client developer for the LINE messaging app.

This article is the latest installment of our weekly series "Improving code quality". For more information about the Weekly Report, please see the first article.

Double-edged tests

Let's say we have a data model FooModel and a class FooModelLogger that logs its information. The FooModelLogger decorates the string properties of FooModel to make them more readable in logs. In the implementation below, constructor arguments are defined to allow the injection of dependent logic for testing.

class FooModel(val title: String, val description: String)

class FooModelLogger(
    private val textLogger: TextLogger = TextLogger(),
    private val joinString: String.(Any?) -> String = String::plus,
    private val decorate: (String) -> String = { text -> LogTextUtility.decorateText(text, "*") },
    private val getTitle: FooModel.() -> String = { this.title },
) {
    fun log(model: FooModel) {
        val titleToLog = model.getTitle()
        val decoratedTitle = decorate(titleToLog)
        val combinedOutput = decoratedTitle.joinString(model.description)
        textLogger.log(combinedOutput)
    }
}

object LogTextUtility {
    fun decorateText(text: String, decoration: String): String = ...
}

By allowing the injection of dependent code in this way, unit tests can focus solely on the logic contained directly within FooModelLogger.

    @Test
    fun `logs title and description with a normal FooModelObject`() {
        val mockTextLogger: TextLogger = mock()
        val mockJoinString: String.(Any?) -> String = { _ -> this + "MockDescription" }
        val mockDecorate: (String) -> String = { "@$it@" }
        val mockGetTitle: FooModel.() -> String = { "MockTitle" }

        val subject = FooModelLogger(
            textLogger = mockTextLogger,
            joinString = mockJoinString,
            decorate = mockDecorate,
            getTitle = mockGetTitle
        )

        val fooModel = FooModel("RealTitle", "RealDescription")
        subject.log(fooModel)

        verify(mockTextLogger).log("@MockTitle@MockDescription")
    }

Is there any problem with this code?

In a nutshell

Injecting too much logic into tests can complicate them and may lead to missing verification of interactions.

Keywords: unit test, test double, injection

List of articles on techniques for improving code quality