stephanos / rewire Goto Github PK
View Code? Open in Web Editor NEWDependency injection for Elixir. Zero code changes required.
Home Page: https://hex.pm/packages/rewire
License: Apache License 2.0
Dependency injection for Elixir. Zero code changes required.
Home Page: https://hex.pm/packages/rewire
License: Apache License 2.0
When I put a call to rewire
in the setup block of my test suite, the tests use the original module instead of the generated one
Given two modules:
It is not possible to (easily?) inject a mock for the client in the connector tests, since:
rewire App.Connector.Hubspot, App.Client.Hubspot: HubspotMock
App.Client.Hubspot
is ambiguous since the connector has the same module nameIt seems plausible it could work with some alias tweaking, but this could be more easily solved by accepting a list of tuples instead of a keyword list, perhaps? Hopefully I can look more into this later.
I've tried using rewire and test coverage but I always get an error on the output of what happened. The cover files get created, and the coverage gets reported, but the full execution fails.
Analysis includes data from imported files
[".../Elixir.CloudWatch.R8837-20673.coverdata"]
** (MatchError) no match of right hand side value: {:error, {:file, <<70, 79, 82, 49, 0, 0, 12, 196, 66, 69, 65, 77, 65, 116, 85, 56, 0, 0, 1, 218, 0, 0, 0, 42, 16, 69, 108, 105, 120, 105, 114, 46, 83, 84, 83, 46, 82, 54, 48, 48, 48, 8, 95, 95, 105, 110, ...>>, :badarg}}
(mix 1.15.0) lib/mix/tasks/test.coverage.ex:292: anonymous fn/3 in Mix.Tasks.Test.Coverage.html/2
(elixir 1.15.0) lib/enum.ex:2510: Enum."-reduce/3-lists^foldl/2-0-"/3
(mix 1.15.0) lib/mix/tasks/test.coverage.ex:291: Mix.Tasks.Test.Coverage.html/2
(mix 1.15.0) lib/mix/tasks/test.ex:578: Mix.Tasks.Test.do_run/3
(mix 1.15.0) lib/mix/task.ex:447: anonymous fn/3 in Mix.Task.run_task/5
(mix 1.15.0) lib/mix/task.ex:502: Mix.Task.run_alias/6
(mix 1.15.0) lib/mix/project.ex:458: Mix.Project.in_project/4
(elixir 1.15.0) lib/file.ex:1624: File.cd!/2
(mix 1.15.0) lib/mix/task.ex:604: anonymous fn/4 in Mix.Task.run_in_children_projects/2
(elixir 1.15.0) lib/enum.ex:2510: Enum."-reduce/3-lists^foldl/2-0-"/3
(mix 1.15.0) lib/mix/task.ex:603: Mix.Task.run_in_children_projects/2
(mix 1.15.0) lib/mix/project_stack.ex:237: Mix.ProjectStack.recur/1
(mix 1.15.0) lib/mix/cli.ex:92: Mix.CLI.run_task/2
(elixir 1.15.0) lib/code.ex:1432: Code.require_file/2
Hey there! I found this library via this blog post and wanted to include it in my project.
One thing I've found is that it doesn't seem to support rewiring modules where the modules must us as:
. Here's an example based on a real-world issue that I'm running up against during a refactor:
defmodule Foo do
# See they have the same final name part so I _must_ use `as:`
alias Foo.YtDlp.CommandRunner, as: YtDlpRunner
alias Foo.Apprise.CommandRunner, as: AppriseRunner
def yt_dlp_bar, do: YtDlpRunner.bar()
def apprise_baz, do: AppriseRunner.baz()
# ...
end
These are both modules that interface with CLI programs. Currently I'm using the standard Mox-recommended method of DI, but I dislike that it puts testing concerns in my main app code. I was hoping I could do something like this using rewire in my tests:
rewire Foo, YtDlpRunner: YtDlpRunnerMock, AppriseRunner: AppriseRunnerMock
# ...
But that doesn't seem to be possible. I could be missing something, but in my testing it seems like the AST is functionally rewritten to this:
defmodule Foo do
alias Foo.YtDlp.CommandRunner, as: YtDlpRunnerMock
alias Foo.Apprise.CommandRunner, as: AppriseRunnerMock
def yt_dlp_bar, do: YtDlpRunnerMock.bar()
def apprise_baz, do: AppriseRunnerMock.baz()
# ...
end
Which only overwrites the as:
alias, but it's still ultimately pointing to the original modules.
Is there a way to handle this and I just missed it? If not, would you be open to a PR that handles this case? This would be a breaking change but I think it's a valuable one
One downside of running tests against a rewired copy of the actual module is that code coverage data is not available anymore. That's because Erlang's cover
modifies tested modules by injecting custom instructions to track the coverage.
rewire
should report the correct test coverage.
prior art: https://github.com/edgurgel/mimic/blob/c7529f8f9607d1ef0725dc723c5337531e5f8326/lib/mimic/cover.ex + https://github.com/eproxus/meck/blob/2c7ba603416e95401500d7e116c5a829cb558665/src/meck_proc.erl
Thanks a lot for the library, it's a very cool idea to utilise aliasing and I really enjoy not having to deal with Application.fetch
!
However, I am facing an issue with nested modules right now.
In my example,
defmodule Messaging do
def send, do: Courier.send()
end
defmodule Courier do
def send, do: Pigeon.send()
end
Pigeon
is the mock that I want to implement, now, if I do:
defmock(PigeonMock, for: PigeonBehaviour)
rewire Messaging, Pigeon: PigeonMock
This compiler gives a nice helpful message and says it can't find Pigeon
in Messaging
, which is only available in Courier.
My actual use case is a little more complicated (with protocol dispatch), but this should illustrate the point just fine.
I can see that it is mentioned in the README,
Only the dependencies of the rewired module will be replaced. Any modules defined around the rewired module will be ignored. All references of the rewired module to them will be pointing to the original. You're always able to rewire them separately yourself.
But is this due to a technical reason, or will it be possible to do it? I'd gladly take a look at it if that's the case.
Since rewire
works by replacing the module with a new version with alias, perhaps we could just recursively walk and replace the modules?
I haven't really looked into where it would work or not, but the proposed idea could look something like:
rewire Messaging, [Courier: [Pigeon: PigeonMock]]
This generates a new Messaging
module that uses a rewired version of Courier
, which uses a rewired version of Pigeon
When rewiring a module in a test, we were noticing that it wasn't replacing all of the occurrences of a module. We eventually tracked down that the module in question was being brought in by a use
macro. When traversing the AST, it appears that the use
special form doesn't get expanded for rewire
, so anything that brings in is left with the original module.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.