4 ms·
> Is there a good reason to have a class hierarchy in modern programming? I have a bad reason :D I like it when objects are simple to instantiate, but sometim
by mrkeen 6y ago
> Is there a good reason to have a class hierarchy in modern programming?
I have a bad reason :D
I like it when objects are simple to instantiate, but sometimes I'm forced to use Spring(Boot) with beans and auto-wiring and such. In src/main, my FooController is autowired up by Spring, but in src/test, I want to instantiate FooController for use in unit tests.
In order to have my cake and eat it too, I extract out all the spring dependencies into a very small shim subclass SpringFooController, which extends FooController. They have all the same methods and types etc, but all of the SpringFooController methods' implementations are just one-liner calls into superclass methods.
What does this get me? SpringFooController is picked up by Spring at runtime, but contains no logic needing to be tested. FooController contains all the logic, but no spring dependencies, so it can be instantiated in unit tests as an ordinary object.
- mercer 6y agoWow. I've not used Spring, but that sounds terribly tedious and indirect. I imagine it's worth it though?
- jimbokun 6y ago"terribly tedious and indirect" would be a pretty accurate slogan for Spring. With all the functionality Spring gives you out of the box, it's probably still worth it. But barely.
- mrkeen 6y agoNot sure if you're asking about Spring or my workaround. Is Spring worth it? The only reason I'd reach for it over something like http://sparkjava.com/ http://sparkjava.com/ is because of its good integration with https://swagger.io/tools/swagger-ui/ https://swagger.io/tools/swagger-ui/. It's the easiest way I know to help out your front-end devs. Whether you like the workaround really depends on how much you dislike Spring, and how much you buy into that whole frameworks-bad-libraries-good argument. As an example of a feature, here's how Spring might validate receiving a book as a POST body. It's as simple as adding @Valid: @PostMapping("/books") Book newBook(@Valid @RequestBody Book newBook) { return repository.save(newBook); } My thoughts on the above: 1) Did i import the right @Valid. There's no compile-time checking, I'll only know it works by trying it. 2) How can I test it? Not in a unit test. Calling newBook and expecting @Valid to fire is a rookie error. I need to spin up some Spring machinery to be able to test anything. 3) Is validation enabled? How do I know? 4) OK, the validator appeared to work in test. Do I have the same Spring config in the test and prod profiles (check yaml, environment variables, CLI args, and @Config objects)? Does validation also work in prod? 5) What does it look like when it rejects input? I assume Spring will return a 400, but what's the error message. Can I set it? Is it plaintext or json? Is it logged? Is it metred? How? 6) Its return type is Book. It always returns a Book? Not an Either<Error, Book>? Not a CompletableFuture<Book>? 7) It accepts a Book. This means that an INVALID BOOK IS INSTANTIATED, and then checked (I think?) after instantiation. This means when you write your Book class, you need to deliberately make invalid state representable, so that Spring has something to validate! Here's the gist of what I'd write instead. (Not a direct comparison, I added features I wouldn't know how to integrate with @Valid) @PostMapping("/books") Response<Book> newBook(@RequestBody String strBook) { return Book.parse(strBook) .handle( (Book book) -> { metrics.something(); logger.something(); repository.save(book) .map(Response::new); }, (Error err) -> { metrics.registerError(err); logger.logError(err); return Response.of(400, "Invalid book submitted: " + err.msg); }); } The method that I wrote can be 'just run' and tested - no need to fire up Spring. You only have a Book immutable and type-safe again. I didn't detail the workaround, but it's essentially this: //SpringBookController extends BookController @PostMapping("/books") Response<Book> newBook(@RequestBody String strBook) { super.newbook(strBook); } // BookController Response<Book> newBook(String strBook) { ... // basically the big example from above minus the @RequestBody and @PostMapping }
- kevinmhickey 6y agoWith the caveat that I have not read your code, I usually solve this same problem by adding a parameter constructor to FooController and annotating it @Autowired. Spring will then inject using the constructor instead of setters or reflection, and your unit tests can construct the object using mocks, stubs, nulls or full object instances.