Let’s assume you want to write a ReSharper plugin for use in VisualStudio. Respectively, this extension accesses services provided by VisualStudio, such as the DTE
(made available by the ReSharper component model) or even services only available via the RawVsServiceProvider
. While this is generally fine, there are certain subtleties to consider when doing it.
The main problem becomes apparent when you want to test your plugin with a R# integration-test project: The tests fail, because the VisualStudio dependencies cannot be injected. In a discussion with Matt Ellis I found out that this is actually to be expected. R# is an environment generally intended for use independent of VisualStudio. Consequently, its test environment loads a standalone R# environment, where VisualStudio dependencies are not available. This has a drawback and an advantage:
- You cannot test your R# plugin’s integration into VisualStudio this way.
- You can replace you VisualStudio dependencies by custom mocks that allow you to control the environment in your tests.
The key to solve the issue is not to let your actual components depend directly on the VisualStudio dependencies, but to introduce a layer of abstraction in between, using R#’s component model. Let’s assume, for example, that you have a component named MyComponent
that depends on a VisualStudio service called SMessageBus
:
Now you make the dependency available in an VisualStudio environment by adding a component like the following to your plugin:
The VS_ADDIN
argument passed to the ShellComponent
attribute lets R# only instantiate this component in a VisualStudio environment. In this case, the RawVServiceProvider
is available and the service can be retrieved. VsMessageBus
is automatically registered to the R# component model as an implementation of IMessageBus
and, thus, injected an creation of MyComponent
.
In you test project, as it runs in a standalone R# environment, the VsMessageBus
component is not loaded. To get your tests to run, you can add a mock implementation of IMessageBus
to the test project, as a ShellComponent
. As it is part of the test project, this mock will never be loaded in production mode. It can implement any behavior you need for you tests, e.g., capture all messages send to the bus in a list for later verification.
A Word on Native VisualStudio Interfaces
After I figured out the above, I expected that for VisualStudio interface exposed by R#, such as DTE
, one could save the effort of implementing the VS_ADDIN
component. While this is true for running in a VisualStudio environment — the DTE
instance is correctly injected –, injection of the mock component — implementing the DTE
interface — in the test project still fails on me. To resolve this I had to introduce a custom interface
and implement this interface with a component in both the production and the test code, as demonstrated for the SMessageBus
service above.