Skip to content

Instantly share code, notes, and snippets.

@limkhashing
Created February 16, 2026 06:29
Show Gist options
  • Select an option

  • Save limkhashing/d1ec743a7cc6dd67e0e614cbb06ea383 to your computer and use it in GitHub Desktop.

Select an option

Save limkhashing/d1ec743a7cc6dd67e0e614cbb06ea383 to your computer and use it in GitHub Desktop.
Learning about Use Case
First of all the UseCase suffix is not necessary. Also capsuling a function into a class where no dependencies are needed is also not necessary. Makes it more complex. You should use operator function "invoke" if you need a class for injecting deps and name the class like a function (ValidateEmail). You can call the instance easily like a function ("validateEmail(...)"). Really good readable.
Example 2 is wrong IMHO. There is no such thing like the representation layer and a dogmatic assignment. Historically the splitting of View and Logic (Presenter, VM, Controller) was because of testing issues. The View isn't really good testable because of the huge framework which it brings with it. Therefore ALL code, which is testable should be tested and the View should have as less conditions (logic) as possible. It was always the responsibility of the Presenter (VM, Controller) to map the Data from different sources into an format the View can understand (ViewState) and which needs no further mapping/conditions (if possible) to be able to test the logic. I recommend lectures from Kent Beck, Ian Cooper, Robert C Martin (Uncle Bob), Martin Fowler.
With the abstractions you are absolutely right.
The problem with testing use cases is explained by Ian Cooper (TDD, Where Did It All Go Wrong). You shouldn't test use cases. Test should never know them. Just call the public interface (for instance ViewModel - on...Clicked()) and check for the out coming ViewState. Only external dependencies like a Database or a Http-Layer (Retrofit) should be mocked. I personally use Koin as dependency framework, which allows to override dependencies. Perfect to replace a Retrofit datasource by a mocked one. Install the production Koin Module + Override definitions in the Test. E.g. in the Test you always test the full path and never call implementation details like Usecases or Repositories. Otherwise the Code isn't really tested and refactoring is mostly impossible. Watch Ian Cooper and try to understand him!!!
A good indicator you do it wrong is when you use VisibleForTesting annotation.
Testing in isolation doesn't mean to test the production classes independent of each other but running the Tests independent of each other ->> Kent Beck
You think too much in Layers and belongings. These things are just there to bring order into the code. Technical depth is fine if you have a good feature test coverage. Spaghetti code is fine if your have a good test coverage. If you test like Kent Beck you can refactor each time and the test will tell you if your features are still work. Your code is frozen, when you test each class and it make zero sense.
Get a little bit more out of usecases is let them be responsible for wrapping in kotlin Result like the last example to make sure errors are caught in the onFailure and appropriate error event are sent to the presentation later from the viewmodel
Propagate exceptions from the repository to the UI and handle them correctly in the UI.
Even a simple database or file reading can throw exceptions. Not to mention api calls when the device have no internet or we receive a timeout exception (these are just some examples. in a real application, we encounter a ton of unexpected error types)
I also use two classes. A base UseCase and a custom Result class
@limkhashing
Copy link
Author

@limkhashing
Copy link
Author

limkhashing commented Feb 16, 2026

Directly use data source on Use Cases instead of abstracting at Repository level

https://www.youtube.com/watch?v=xrxC5gRNV48

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment