Use MessageData with InMemoryTestHarness - masstransit

I try to setup an integration test using the in memory test harness of MassTransit. Since my MyCommandConsumer receives big binary data via the MessageData abstraction I need to configure the repository:
var provider = new ServiceCollection()
.AddMassTransitInMemoryTestHarness(cfg =>
{
// this does not work:
cfg.UsingInMemory((context, conf) => conf.UseMessageData(repository));
cfg.AddConsumer<MyCommandConsumer>();
cfg.AddConsumerTestHarness<MyCommandConsumer>();
})
.BuildServiceProvider(true);
Unfortunately the code above does not work yielding an exception:
MassTransit.ConfigurationException: 'SetBusFactory' can be called only once.
How can I configure the InMemoryTestHarness to use a MessageData provider?

You need to resolve the test harness, and then add a configuration event. This should be placed before the call to Start the harness:
TestHarness = provider.GetRequiredService<InMemoryTestHarness>();
TestHarness.OnConfigureInMemoryBus += configurator =>
{
configurator.UseMessageData(repository);
};

Related

Custom consumer implementation factory with Microsoft Dependency Injection

Is there a way to register Consumer like the service below:
services.AddTransient < IMyService > (provider => {
return new MyServiceImplementation(2);
});
with AddConsumer<T>() method?
What I need is a custom implementation of Consumer factory because it will be injected with a different instance of one of its dependencies depending on the configuration.
MassTransit registers the consumer added via AddConsumer as shown below:
collection.AddScoped<T>();
You're welcome to create your own register after configuring MassTransit, which should replace the one registered by MassTransit. In your example above, it could be something like:
services.AddScoped<TConsumer>(provider =>
{
var options = provider.GetService<SomeOptions>();
if (options.UseFirst)
return new TConsumer(provider.GetRequiredService<Impl1>()
return new TConsumer(provider.GetRequiredService<Impl2>()
});
You get the picture, right?

Apollo conditional data sources & initialization lifecycle

I have a specific use case where a user’s data sources are conditional - e.g based on the data sources saved in the database for every specific user.
This also means every data source has unique credentials for every user, which is fine for RESTDataSource because I can use the willSendRequest to set the Authentication headers before each request.
However, I have custom data sources that have proprietary clients (for example JSForce for Salesforce) - and they have their own fetch mechanism.
As of now - I have a custom transformer directive that fetches the tokens from the database and adds it into the context - however, the directive is ran before the dataSource.initialize() method - so that I can’t use the credentials there because the context still doesn’t have it.
I also don’t want to initialize all data sources for every user even if he doesn’t use said data source in this request - but the dataSources() function doesn’t accept any parameter and is not contextual.
Bottom line is - is it possible to pass data sources conditionally based even on the Express request? When is the right time to pass the tokens and credentials to the dataSource? Maybe add my own custom init function and call it from the directive?
So you have options. Here are 2 choices:
1. Just add your dataSources
If you just initialize all dataSources, internally it can check to see if the user has access. You could have a getClient function that resolves on the client or throws an UnauthorizedError, depending.
2. Don't just add your dataSources
So if you really don't want to initialize the dataSources at ALL, you can absolutely do this by adding the "dataSources" yourself, just like Apollo does it.
const server = new ApolloServer({
// this example uses apollo-server-express
context: async ({ req, res }) => {
const accessToken = req.headers?.authorization?.split(' ')[1] || ''
const user = accessToken && buildUser(accessToken)
const context = { user }
// You can't use the name "dataSources" in your config because ApolloServer will puke, so I called them "services"
await addServices(context)
return context
}
})
const addServices = async (context) => {
const { user } = context;
const services = {
userAPI: new UserAPI(),
postAPI: new PostAPI(),
}
if (user.isAdmin) {
services.adminAPI = new AdminAPI()
}
const initializers = [];
for (const service of Object.values(services)) {
if (service.initialize) {
initializers.push(
service.initialize({
context,
cache: null, // or add your own cache
})
);
}
}
await Promise.all(initializers);
/**
* this is where you have to deviate from Apollo.
* You can't use the name "dataSources" in your config because ApolloServer will puke
* with the error 'Please use the dataSources config option instead of putting dataSources on the context yourself.'
*/
context.services = services;
}
Some notes:
1. You can't call them "dataSources"
If you return a property called "dataSources" on your context object, Apollo will not like it very much [meaning it throws an Error]. In my example, I used the name "services", but you can do whatever you want... except "dataSources".
With the above code, in your resolvers, just reference context.services.whatever instead.
2. This is what Apollo does
This pattern is copied directly from what Apollo already does for dataSources [source]
3. I recommend you still treat them as DataSources
I recommend you stick to the DataSources pattern and that your "services" all extend DataSource. It's going to be easier for everyone involved.
4. Type safety
If you're using TypeScript or something, you're going to lose a bit of type safety, since the context.services is either going to be one shape or another. Even if you're not, if you're not careful, you may end up throwing "Cannot read property users of undefined" errors instead of "Unauthorized" errors. You might be better off creating "dummy services" that reflect the same object shape but just throw Unauthorized.

Need wire to emit interfaces for client and server in same gradle project

I want to use interfaces for both client and server in the same android app. Usecase is to run a okhttpmockwebserver serving gRPC requests within the same app the client is running in. For this i created two library projects with their own wire configuration for client and server similar to those
wire {
kotlin {
includes = ['com..caompany.android.proto.*']
out "${buildDir}/protos"
rpcCallStyle = 'suspending'
rpcRole = 'client'
}
}
wire {
kotlin {
includes = ['com..company.android.proto.*']
out "${buildDir}/protos"
rpcCallStyle = 'suspending'
rpcRole = 'server'
}
}
Executing the wire-gradle-plugin fails with this exception:
com.company.android.proto.HelloReply$Companion$ADAPTER$1 is defined multiple times.
Caused by: com.android.tools.r8.CompilationFailedException: Compilation failed to complete, origin: .../com/company/android/proto/HelloReply$Companion$ADAPTER$1.dex
It would help me if wire could either
Generate all classes and interfaces at once including server and client role or
Exclude the generation of class files, only generating service interfaces for client or server
Is there a workaround i can achieve a similar result without gradle plugin support?
You can have multiple kotlin blocks at the same time. Wire will throw if you generate the same class twice so you need to define the rule as unique between both.
You need one block which will generate client role interfaces. You need one block to generate server roles interfaces. Lastly, you need to generate regular types in yet another block, or in one of them (but not both).
Something like this
wire {
kotlin {
includes = ['all.services.or.package']
rpcCallStyle = 'suspending'
rpcRole = 'client'
}
kotlin {
includes = ['all.services.or.package']
rpcCallStyle = 'suspending'
rpcRole = 'server'
}
kotlin {
excludes = ['all.services.or.package']
rpcRole = 'none'
}
}

C# ElasticSeach Mock Suggestions

I am trying to mock a suggest response, however suggestionOption.Setup(x => x.Text).Returns("Hello") is throwing an exception:
An exception of type System.NotSupportedException occurred in
Moq.dll but was not handled in user code Additional information:
Invalid setup on a non-virtual (overridable in VB) member: x => x.Text
var searchSuggestResponseMock = new Mock<ISuggestResponse>();
var suggestionOption = new Mock<SuggestOption>();
suggestionOption.Setup(x => x.Text).Returns("Hello");
suggestionOption.Setup(x => x.Payload).Returns("{path:\"drugs/hello\"}");
var suggestion = new Mock<Suggest>();
suggestion.Setup(x => x.Options).Returns(new List<SuggestOption> { suggestionOption.Object });
searchSuggestResponseMock.Setup(x => x.Suggestions).Returns(new Dictionary<string, Suggest[]>()
{
{"suggest", new Suggest[] {suggestion.Object}},
});
var mock = new Mock<IConnector>();
mock.Setup(x => x.getClient()
.Suggest<Term>(Moq.It.IsAny<Func<SuggestDescriptor<Term>,
SuggestDescriptor<Term>>>())).Returns(searchSuggestResponseMock.Object);
_connector = mock.Object;
You can't mock non-virtual methods. As the error states:
Invalid setup on non-virtual member
Moq does its magic by acting as a proxy between your code and the real class. It does this by taking advantage of virtual methods. Without having a virtual method, Moq can't intercept the call.
Neither SuggestionOption, or Suggest are easily mockable, as they have non-virtual, internal set based properties, and do not implement any specific interface.
It looks like you are maybe mocking at too low a level. If you don't want to call Elastic to get your list of suggestions then have a method which just returns an array of strings (or your own custom Suggestion class) and mock that instead.
Or just call Elastic for real, as long as you are passing in sensible values which don't return thousands of suggestions.
(Or you could in theory create instances of Suggest, and set the internal properties via reflection, but this is not ideal obviously).

MOQ - Mock a Dictionary<string, double> object

I have the following setup for Moq:
... other code to setup bigMoq object ...
var innerMoq = new Mock<IDictionary<string, double>>();
innerMoq.SetupGet(d => d["COMPLEX"]).Returns(6d);
innerMoq.SetupGet(d => d["MEDIUM"]).Returns(8d);
innerMoq.SetupGet(d => d["SIMPLE"]).Returns(10d);
bigMoq.SetupGet(d => d.ComplexityWeights).Returns(x.Object);
When running a test method, I pass in bigMoq as the arguement.
The following works:
bigMoqVar.ComplexityWeights["COMPLEX"] // correctly returns 6
However, this does not:
bigMoqVar.ComplexityWeights.ContainsKey("COMPLEX") // returns false instead of true
What is the recommended way to support the ContainsKey on the innerMoq?
That's because you didn't setup an expectation for ContainsKey. You will need to setup that manually, Moq has no knowledge of the semantics of the interface.
innerMoq.Setup(d => d.ContainsKey("COMPLEX")).Returns(true);
However, if this is just an IDictionary you need, why go through the mocking framework ? Just create a Dictionary<string,double> with the objects you need.

Resources