why kotlinx.datetime.LocalDateTime class does not ...
# kotlinx-datetime
u
why kotlinx.datetime.LocalDateTime class does not have now() method unlike java.time.LocalDateTime?
j
because that would implicitly have a dependency on the system clock and break your ability to test
๐Ÿ‘€ 2
โž• 2
u
Copy code
class TestClock : Clock {

    private var testInstant: Instant? = null

    override fun now(): Instant {
        return testInstant ?: Clock.System.now()
    }

    fun fixAt(time: LocalDateTime) {
        testInstant = time.toInstant(TimeZone.UTC)
    }

    fun reset() {
        testInstant = null
    }
}
you mean using fake clock instance for test like this?
j
there's one built-in to the library that you can use
๐Ÿ‘€ 1
oh maybe not. i'm thinking of the stdlib and time source
thank you color 1
m
I have these util metods
Copy code
fun nowAsInstant() = Clock.System.now()

fun nowAsLong() = nowAsInstant().toEpochMilliseconds()
๐Ÿ‘ 1
p
We do the same as โ˜๏ธ with utilities
k
I think the idea is to pass a
Clock
instance as a parameter to any method (or any class constructor) that needs the time. Pass it
Clock.System
in production, and
TestClock
in unit tests, where you define
TestClock.now()
to return a test value. Something analogous to
java.time.Clock.systemUTC()
vs
java.time.Clock.fixed()
.
โž• 1
c
Copy code
fun nowAsInstant() = Clock.System.now()
That's really bad: it means you can't switch the clock to fake times. You're going to regret this a lot when you need to debug something that only happens on a very specific date
p
I suppose that if you have methods that rely on time to give a result, you can declare them with
milliseconds: Long
as an input, even now date should be an input. With that way you wonโ€™t have the need for Clock.
m
Thatโ€™s really bad: it means you canโ€™t switch the clock to fake times.
It will happen only if you use these directly in a class, but you can have interfaces to abstract away these implementations as a result you can always mock them. ie.
Copy code
every { appStorage.premiumEndDate }
            .returns(nowAsLong() + 1.seconds.inWholeMilliseconds)
๐Ÿ‘ 3
c
โ€ฆWhy use a mocking framework when you could just pass the
Clock
?
๐Ÿ‘ 1
j
Mocking objects should be an anti-pattern.
๐Ÿ’ฏ 4
๐Ÿ‘๐Ÿพ 1
๐Ÿ‘ 7
m
True, I do not disagree with using the Clock itself and passing test implementation when necessary, it is just matter of the preference between simplicity and perfection, at the end it is up to the project and its needs
u
@Javier Why mocking object should be anti pattern?
j
@์œค๋™ํ™˜ because it is often a smell that the design of the code is not testable in itself by using regular means, and that, in turn, is a smell that the design is not modular/extensible enough.
u
I learned dependency inversion is one of the best idea of make untestable code to testable! So that means we change the normal class to interface. And on test code, we should implementation the interface as a Fake Object and use it for test rather than mocking untestable code. You think implementation fake object every test is better than mock object every time? Sometimes, implementation object every time feels boringโ€ฆ and by the make testable code, the production code should change is not feel so good
j
Fakes should be shared, possibly even tested themselves
โž• 2
๐Ÿ˜€ 1
You don't need 10,000 FakeClocks, you only need one.
โž• 3
๐Ÿ˜€ 1
u
Aha great! So other Clock case, in a most cases, you think dependency inversion by interface on test should be considered before using mocking framework?
j
Absolutely. I haven't needed mocking in almost a decade at this point. Used to be the first thing I would turn to. Now it's the last.
thank you color 2
๐Ÿ‘ 4
u
Thank you Jake! It was helpful answer for me!
c
Fakes should be shared, possibly even tested themselves
If anyone's interested, here's a codebase that does that: https://gitlab.com/opensavvy/formulaide The domain layer (
:core
) contains data classes and service declarations as interfaces. The
:fake
module contains in-memory implementations of all these interfaces. The
:test
module contains test suites that check the validity of an implementation. All implementations of the interface (
:fake
which is in-memory,
:mongo
which handles persistence,
:remote
for client-server communication, etc) are all tested using the same test suite. I'm experimenting with having UI tests also use the same suite. No matter what technologies you use, the only real requirement for doing that is to be able to declare tests programmatically. In that project, I'm hacking around through JUnit5 test inheritance and Kotlin/JS's test runners internalsโ€ฆ I'm working on releasing that test harness as a standalone library, but any other test framework with this feature (e.g. Kotest) would work as well.
๐Ÿ‘€ 1
140 Views