One of the ideas behind IoC frameworks (which build on top of DI) is that you could swap out implementation classes. For a great deal of software (and especially in cloud-hosted, SaaS style microservice architecture) the test stubs are the only other implementations that ever get injected.
Most code bases could ditch IoC if Java provided a language-level construct, even if that construct were only for the test harness.
Spring is great when you need that dynamic control at runtime (especially when code dependencies are separated by modules) but you're just aping what good dynamic languages like Clojure or Common Lisp give you for free. But I can't complain too much, developing modern Java with its popular frameworks and with JRebel is getting closer to the Lisp experience every year, I'd rather have that than for Java to remain stagnate like in its 1.6/1.7 days.
Can you show me how you would mock the current time of that method in Java?
It's one line of Ruby/Javascript code to do that.
final Date testDate = someFixedDate;
User testUser = new User() {
@Override
Date say_current_time() {
return testDate;
}
};
If it is private and/or static, you can get around it without having to change the code, but if you own the code, you should just do that... Often the change will be as simple as replacing some method's raw usage of Date.now() with a local say_curent_time() method that uses it or some injected dependency just so you can mock Date.now() without hassle.But your point further down that in Java you have to think about your code structure more to accommodate tests is valid. I think it's easy to drink the kool-aid and start believing that many code structuring styles that enable easier testing in Java are actually very often just better styles regardless of language, but you're not going to really see the point if you do nothing but Ruby/JS where you can get away with not doing such things for longer. Mostly it has to do with dynamic languages offering looser and later and dynamic binding than static languages (which also frequently makes them easier to refactor even if you don't have automated tools). One big exception is if your language supports multiple dispatch, a lot of super ugly Java-isms go away and you shouldn't emulate them. The book Working Effectively with Legacy Code is a good reference for what works well in Java and C++ (and similar situations in other languages), it's mostly about techniques for breaking dependencies.
Mockito in Java has a nifty way of doing this with Mockito.mockStatic:
@Test
public void mockTime() throws InterruptedException {
LocalDateTime fake = LocalDateTime.of(2021, 7, 2, 19, 0, 0);
try (MockedStatic<LocalDateTime> call = Mockito.mockStatic(LocalDateTime.class)) {
call.when(LocalDateTime::now).thenReturn(fake);
assertThat(LocalDateTime.now()).isEqualTo(fake);
Thread.sleep(2_000);
assertThat(LocalDateTime.now()).isEqualTo(fake);
}
LocalDateTime now = LocalDateTime.now();
assertThat(now).isAfter(fake);
assertThat(now).isNotEqualTo(fake);
}
Or you can pass a Clock instance and use .now(clock). That Clock then can be either a system clock or a fixed value. Instant testNow = ...
User u = new User(Clock.fixed(testNow, ZoneOffset.UTC));
u.sayCurrentTime();
although it would be better design to have sayCurrentTime take a date parameter instead of depending on an external dependency. User mock = mock(User.java)
when(mock.say_current_time()).thenReturn(someDate)Mocking dynamically typed languages is monkey patching, something that the industry has been moving away from for more than a decade. And for good reasons.
I can say the same about Rails + RSpec. It exists therefore it's good.
> Mocking dynamically typed languages is monkey patching, something that the industry has been moving away
That's a reach. There are millions of javascript/python/php/ruby/elixir devs that don't use types or annotations. They mock. "The industry" isn't one cohesive thing.