Fl4m3Ph03n1x
Background
I have an umbrella application that has several apps inside. These apps communicate and depend on each other.
So, when I am testing, because I am using mocks, I need to mock their interfaces. Now if you know about mocks or if you have read Jose Valim’s opinion on them (Mocks and explicit contracts « Plataformatec Blog) you know this brings an issue:
- Using Mocks means I can deviate from the real implementation.
App Structure
Imagine I have an umbrella project with 2 apps:
- cli (a command line interface)
- manager (where all the logic is)
Umbrella Project structure
.
├── README.md
├── apps
│ ├── cli
│ └── manager
├── config
│ └── config.exs
├── mix.exs
└── mix.lock
Now, every time I change the manager’s Public API, I have to update the interface on cli, otherwise my tests in cli will pass even though nothing will then work:
cli app stucture
.
├── README.md
├── lib
│ ├── cli.ex
│ └── manager.ex # interface for the manager umbrella app
├── mix.exs
└── test
├── cli_test.exs
├── support
└── test_helper.exs
Problems
So, following this pattern, I have a few issues:
- every time create a new
@typein themanager, I have to update all the interfaces that use it, so the projects usingmanageras a dependency can have access to the new types I created. - every time I change
manager’s public API, I have to update the projects that use it as a dependency - I have a
manager.exfile in mycliapp, that is just an interface for themanagerapp. This is confusing, as people reading my code will not intuitively understand this is just an interface so i can create a mock that obeys it (mocks as nouns :D)
Possible solutions?
Given these issues I have considered the following option:
- create a new app, called
interfaces, where I define the interfaces of all umbrella apps.
This would have the following benefits:
- Changes to the Public API of any app would always be reflected there.
- All dialyzer
@types would be there as well - Any project using the Interface app as a dependency would have immediate access to the most updated interfaces and type specs.
- I would get rid of the
manager.exfile because I would import it from theinterfaceapp
It would however have the following drawbacks:
- Any umbrella app would be forced to have 2 dependencies: the
interfaceapp, and the app they want to use (in this case,cliwould have to import bothinterfaceandmanager) - They would have access to all interfaces and type specs, even if not needed
- I would still have to change things in two places: the
interfaceapp, and the app that implements the interface.
So I am not really sure on how to deal with this issue.
How do you guys deal with this?
Trending in Discussions
Other Trending Topics
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #library
- #deployment
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #elixirconf
- #channels
- #exunit
- #discussion
- #code-sync
- #podcasts
- #javascript
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixirconf-us
- #ai
- #blog-post
- #elixir-ls
- #phoenix_html
- #iex
- #graphql
- #genstage
- #websockets
- #supervisor
- #advent-of-code
- #distillery
- #processes
- #api
- #forms
- #metaprogramming
- #hex
- #security










Showing Posts 1 to 10- Show Best Posts
- Show All (oldest first)
- Show All (newest first)
LostKobrakai
Doesn’t
:clihave{:manager, in_umbrella: true}? Then:clican use anything (public) in:manager.Fl4m3Ph03n1x
yes, it does. But I still have to create a
manager.exfile that is an interface so I can use Mox.And when you say public, you also mean
@types ?LostKobrakai
I guess we already had this conversation, but I personally would not mock internal dependencies in the first place. But
manager.exis one file in:cli, which belongs to:cliand is built to its needs. What exactly would you be copying from:managerand why?Fl4m3Ph03n1x
manager.exis a behaviour, that represents the public API of themanagerapp.The
managerapp is an implementation of themanager.exbehaviour.I have this behaviour so I can mock its implementation with Mox.
How would you solve this without mocks? Inject the dependencies directly?
You would end up with the same issue in the end, in the sense that a change in
managerwould always require another incli.I have a feeling this discussion comes up often in different ways because I have not yet cemented a solution
Or perhaps this is something more general and I am not seeing the full picture.
LostKobrakai
So either
:clidepends on:managerand takes what it gets or:managerdepends on:cliand implements the interface:cliprescribes. If you do both you’re in trouble. You don’t want circular dependencies.Also what you’re seeing here is partly the cost of decoupling things, just that you’re not yet seeing any benefits out of that, because you’re only doing it for mocking and not for general decoupled parts in your system.
Fl4m3Ph03n1x
So, a few questions:
interfaceapp idea? Overkill?This discussion is making me realize that perhaps the behaviour should be in the
managerapp instead of thecliapp.Good discussion!
LostKobrakai
If there’s no need on the manager side I’d test
:cliby letting it call into:managerdirectly. No need to switch implementations without good reason. I also wouldn’t opt for an “interface” app yet. If you actually decouple those apps at some point (no hardcoded dependencies anymore) a shared interface might make sense.Fl4m3Ph03n1x
Those apps are decoupled because my manager can have different types of interfaces.
cliis just one of them, I will eventually add a phoenix-live interface that talks to themanager.This is way decoupling was something I did in this small project.
If I allow
clito callmanagerdirectly, then I will be doing end to end tests only, that is not my objective. Or perhaps I miss understood you?LostKobrakai
Yeah, but
:clidoesn’t need to be able to interact with multiple backends I imagine.:manageris the only one it interacts with. So no decouling in that direction.So what? To me this is not a problem in itself. If there’s something in manager, which makes testing hard, this might change. But for that fact of the part making it hard, not because I want to not have end to end tests.
egze
Don’t know if it helps, but here are my 2 cents how we solve it.
We have the following umbrella structure:
api_clientexposes this functionApiClient.Companies.listappuses theapi_clientapp as a dependency (in_umbrella: true).Now let’s say we are writing tests for
appthat do something with whatApiClient.Companies.listreturns. First we create a wrapper forApiClientinside theapp. So we never callApiClient.Companies.listoutside of this wrapper directly. This is also where we introduce a new behaviour and use Mox to create a mock for it.