How to chain reactive operations after empty Mono without blocking? - reactive-programming

Basically what I am trying to achieve is to call a second repository (a ReactiveCrudRepository) or throw an exception, depending on the result of a call to a first repository.
My original idea looks like this:
/** Reactive with blocking code */
public Flux<SecondThing> getThings(String firstThingName) {
FirstThing firstThing = firstRepo
.findByName(firstThingName)
// Warning: "Inappropriate blocking method call"
.blockOptional() // this fails in test-context
.orElseThrow(() -> new FirstThingNotFound(firstThingName));
return secondRepo.findAllByFirstThingId(firstThing.getId());
}
Which would correspond to the following non-reactive approach:
/** Non-reactive */
public List<SecondThing> getThings(String firstThingName) {
FirstThing firstThing = firstRepo
.findByName(firstThingName)
.orElseThrow(() -> new FirstThingNotFound(firstThingName));
return secondRepo.findAllByFirstThingId(firstThing.getId());
}
I haven't found a way to do this in a reactive non-blocking way. All I need is to throw an error if an empty Mono comes out of the first call, and continue the pipeline if not empty; but I could not seem to use onErrorStop or doOnError correctly here, and map does not help as it skips the empty Mono.
What I have is a workaround if I use id instead of name, but I'm not quite satisfied with it as it shows a different behaviour in the case where is an instance of FirstThing but no SecondThing linked to it:
/** Reactive workaround 1 */
public Flux<SecondThing> getThings(Long firstThingId) {
return secondRepo
.findAllByFirstThingId(firstThingId)
.switchIfEmpty(
Flux.error(() -> new FirstThingNotFound(firstThingName))
);
}
Another workaround I've found is the following, that replaces the empty Mono with a null value, but it doesn't look right and throws a warning too:
/** Reactive workaround 2 */
public Flux<SecondThing> getThings(String firstThingName) {
return firstRepo
.findByName(firstThingName)
// Warning: "Passing 'null' argument to parameter annotated as #NotNull"
.defaultIfEmpty(null)
.flatMapMany(
firstThing -> secondRepo.findAllByFirstThingId(firstThing.getId()
)
.onErrorMap(
NullPointerException.class, e -> new FirstThingNotFound(firstThingName)
);
}
What is the correct way to chain the calls to both repositories so that the presence or absence of a FirstThing with the requested firstThingName conditions the call to the second repo?

I found a solution so simple, that I could be ashamed not to have found it earlier:
public Flux<SecondThing> getThings(String firstThingName) {
return firstRepo
.findByName(firstThingName)
.switchIfEmpty(Mono.error(() -> new FirstThingNotFound(firstThingName)))
.flatMapMany(
firstThing -> secondRepo.findAllByFirstThingId(firstThing.getId()
);
}
The trick is that switchIfEmpty does not force you to pick a "valid" replacement value, so it is possible to use a Mono.error to propagate the right exception directly.

Related

Reactive programming - With default and exception handling

I am trying to find the best approach for default and error handling with reactive.
This is a working code.
public Mono<ServerResponse> retrieveById(ServerRequest request) {
final String id = request.pathVariable("ID");
return context.retrieveUser().flatMap(usr -> {
return repository.findByApptId(Long.parseLong(id), usr.getOrgId()).flatMap(appt -> {
return ServerResponse.ok().contentType(APPLICATION_JSON).bodyValue(appt);
});
});
}
I am trying to add default and error handling.
For default,
return context.retrieveUser().flatMap(usr -> {
return repository.findByApptId(Long.parseLong(apptId), usr.getOrgId()).flatMap(appt -> {
return ServerResponse.ok().contentType(APPLICATION_JSON).bodyValue(appt);
}).defaultIfEmpty(ServerResponse.notFound().build());
The above default add gives error.
The method defaultIfEmpty(ServerResponse) in the type
Mono is not applicable for the arguments
(Mono)
Same error with onErrorReturn as well.
Mono#defaultIfEmpty takes an item T and returns a Mono<T>. Which means that if you give it a String it will return a Mono<String>.
You are giving it ServerResponse.notFound().build() which returns a Mono<ServerResponse> which won’t work since it would give Mono<Mono<ServerResponse>>.
What you are looking for is Mono#switchIfEmpty which switches producer (Mono) to another producer if the previous one turned out empty.

Using project reactor mergeWith() operator in order to achieve "if/elseif/else" branching logic

I am trying to use project reactor mergeWith operator in order to achieve a if/elseif/else branching logic as described here: RxJS, where is the If-Else Operator.
The provided samples are written in RxJS but the underlying idea remains the same.
Basically the idea is to use the filter operator on 3 monos/publishers (therefore with 3 different predicates) and merge the 3 monos as follows (here they are RxJS Observables of course):
const somethings$ = source$
.filter(isSomething)
.do(something);
const betterThings$ = source$
.filter(isBetterThings)
.do(betterThings);
const defaultThings$ = source$
.filter((val) => !isSomething(val) && !isBetterThings(val))
.do(defaultThing);
// merge them together
const onlyTheRightThings$ = somethings$
.merge(
betterThings$,
defaultThings$,
)
.do(correctThings);
I have copied and pasted the relevant sample from the above article.
Consider that something$, betterThings$ and defaultThings$ are our monos isSomething & isBetterThings are the predicates.
Now here are my 3 real monos/publishers (written in java):
private Mono<ServerResponse> validateUser(User user) {
return Mono.just(new BeanPropertyBindingResult(user, User.class.getName()))
.doOnNext(err -> userValidator.validate(user, err))
.filter(AbstractBindingResult::hasErrors)
.flatMap(err ->
status(BAD_REQUEST)
.contentType(APPLICATION_JSON)
.body(BodyInserters.fromObject(err.getAllErrors()))
);
}
private Mono<ServerResponse> validateEmailNotExists(User user) {
return userRepository.findByEmail(user.getEmail())
.flatMap(existingUser ->
status(BAD_REQUEST)
.contentType(APPLICATION_JSON)
.body(BodyInserters.fromObject("User already exists."))
);
}
private Mono<ServerResponse> saveUser(User user) {
return userRepository.save(user)
.flatMap(newUser -> status(CREATED)
.contentType(APPLICATION_JSON)
.body(BodyInserters.fromObject(newUser))
);
}
Here is the top level method that needs to merge the three publishers:
public Mono<ServerResponse> signUpUser(ServerRequest serverRequest) {
return serverRequest.bodyToMono(User.class)
.mergeWith(...)
}
I am not sure how to use the mergeWith() operator... I have tried the Mono.when() static operator which takes several publishers (good for me) but returns a Mono<void> (bad for me).
Can anyone please help?
P.S. I am sure you will excuse the mix between RxJS (js) and Reactor code (java). I meant to use my knowledge from RxJS in order to achieve a similar goal in my Reactor app. :-)
edit 1: I have tried this:
public Mono<ServerResponse> signUpUser(ServerRequest serverRequest) {
return serverRequest
.bodyToMono(User.class)
.flatMap(user -> validateUser(user).or(validateEmailNotExists(user)).or(saveUser(user))).single();
}
But I get this error: NoSuchElementException: Source was empty
edit 2: Same with (notice the parenthesis):
public Mono<ServerResponse> signUpUser(ServerRequest serverRequest) {
return serverRequest
.bodyToMono(User.class)
.flatMap(user -> validateUser(user).or(validateEmailNotExists(user)).or(saveUser(user)).single());
}
edit 3: Same error with a Mono<User>:
public Mono<ServerResponse> signUpUser(ServerRequest serverRequest) {
Mono<User> userMono = serverRequest.bodyToMono(User.class);
return validateUser(userMono)
.or(validateEmailNotExists(userMono))
.or(saveUser(userMono))
.single();
}
edit 4: I can confirm that at least one of the three monos will always emit. It is when I use the or() operator that something goes wrong...
If I use this, all my tests pass:
public Mono<ServerResponse> signUpUser(ServerRequest serverRequest) {
return serverRequest.bodyToMono(User.class)
.flatMap(user -> Flux.concat(validateUser(user), validateEmailNotExists(user), saveUser(user)).next().single());
}
I have used the concat() operator here to preserve the order of operations.
Do you know what I am getting wrong with the or() operator?
edit 5: I have tried with the cache() operator as follows to no avail:
public Mono<ServerResponse> signUpUser(ServerRequest serverRequest) {
return serverRequest
.bodyToMono(User.class)
.cache()
.flatMap(user -> validateUser(user)
.or(validateEmailNotExists(user))
.or(saveUser(user))
.single()
);
}
Your current code sample implies that your 3 methods returning Mono<ServerResponse> should be taking a Mono<User> rather than a User, so you may need to alter something there.
However, I digress - that doesn't seem to be the main question here.
From what I understand of the pattern described in that link, you're creating 3 separate Mono objects, only one of which will ever return a result - and you need a Mono of whichever one of your original 3 Mono objects returns.
In that case, I'd recommend something like the following:
Mono<ServerResult> result = Flux.merge(validateUser(user), validateEmailNotExists(user), saveUser(user)).next().single();
Breaking it down:
The static Flux.merge() method takes your 3 Mono objects and merges them into a Flux;
next() returns the first available result as a Mono;
single() will ensure that the Mono emits a value, as oppose to nothing at all, and throw an exception otherwise. (Optional, but just a bit of a safety net.)
You could also just chain Mono.or() like so:
Mono<ServerResult> result = validateUser(user).or(validateEmailNotExists(user)).or(saveUser(user)).single();
The advantages to this approach are:
It's arguably more readable in some cases;
If it's possible that you'll have more than one Mono in your chain return a result, this allows you to set an order of precedence for which one is chosen (as oppose to the above example where you'll just get whatever Mono emitted a value first.)
The disadvantage is potentially one of performance. If saveUser() returns a value first in the above code, then you still have to wait for the other two Mono objects to complete before your combined Mono will complete.

How does the logging module for Autofac and NLog work?

I am still fairly new to Autofac and Nlog and I need some help in understanding what is taking place in my Autofac LoggingModule for Nlog. It works as expected thanks to following the injecting-nlog-with-autofacs-registergeneric. But rather than just copy paste, I would like to make sure I understand what is occurring in each method (Load & AttachToComponentRegistration). If you could review my thoughts and further clarify anything I have incorrect (quite a bit I am sure), I would greatly appreciate it. Thank you in advance!
Database Target using Nlog
Dependency Injection using Autofac
ASP.NET MVC web app for learning
Dvd Libary app (DvdAdd, DvdEdit, DvdDelete, DvdList)
LoggingModule
public class LoggingModule : Module
{
protected override void Load(ContainerBuilder builder)
{
builder
.Register((c, p) => new LogService(p.TypedAs<Type>()))
.AsImplementedInterfaces();
}
protected override void AttachToComponentRegistration(IComponentRegistry componentRegistry, IComponentRegistration registration)
{
registration.Preparing +=
(sender, args) =>
{
var forType = args.Component.Activator.LimitType;
var logParameter = new ResolvedParameter(
(p, c) => p.ParameterType == typeof(ILog),
(p, c) => c.Resolve<ILog>(TypedParameter.From(forType)));
args.Parameters = args.Parameters.Union(new[] { logParameter });
};
}
}
My understanding of the code within Load()
c - The parameter c, provided to the expression, is the component context(an IComponentContext object) in which the component is being created. The context in which a service can be accessed or a component's dependencies resolved.
p - An IEnumerable with the incoming parameter set
AsImplementedInterfaces - Autofac allows its users to register the types explicitly or implicitly. While "As" is used for explicit registrations, "AsImplementedInterfaces" and "AsSelf" are used for implicit ones. In other words, the container automatically registers the implementation against all the interfaces it implements.
Thoughts: The Load method code registers a new LogService class (which represents "c") with the type of logger (which represents "p") as the constructor parameter for the LogService class
Questions:
Are my thoughts above correct?
Should it be SingleInstance or should it / will it only live as long as the calling classes scope? (I am thinking about my Unit Of Work)
My understanding of the code within AttachToComponentRegistration()
AttachToComponentRegistration method - Override to attach module-specific functionality to a component registration.
AttachToComponentRegistration Parameters:
IComponentRegistry componentRegistry - Provides component registrations according to the services they provide.
IComponentRegistration registration - Describes a logical component within the container.
registration.Preparing - Fired when a new instance is required. The instance can be provided in order to skip the regular activator, by setting the Instance property in the provided event arguments.
var forType = args.Component.Activator.LimitType;
args = Autofac.Core.PreparingEventArgs - Fired before the activation process to allow parameters to be changed or an alternative instance to be provided.
Component = PreparingEventArgs.Component Property - Gets the component providing the instance being activated
Activator = IComponentRegistration.Activator Property - Gets the activator used to create instances.
LimitType = IInstanceActivator.LimitType Property - Gets the most specific type that the component instances are known to be castable to.
Thoughts on forType - As I understand it, this variable holds the Name and FullName of the calling class from where the logging service is being called?
forType Debugger Image
Questions:
Are my thoughts forType correct?
var logParameter = new ResolvedParameter(
(p, c) => p.ParameterType == typeof(ILog),
(p, c) => c.Resolve<ILog>(TypedParameter.From(forType)));
ResolvedParameter - can be used as a way to supply values dynamically retrieved from the container,
e.g. by resolving a service by name.
Thoughts on logParameter - This is where I start to get lost. So does, it check that the Parameter is of Type ILog and if so it will then resolve it with the constructor parameter and pass in forType variable?
Questions:
Are my thoughts on logParameter above correct?
args.Parameters = args.Parameters.Union(new[] { logParameter });
args.Parameters = PreparingEventArgs.Parameters Property - Gets or sets the parameters supplied to the activator.
args.Parameters.Union = Produces the set union of two sequences by using the default equality comparer. Returns an System.Collections.Generic.IEnumerable`1 that contains the elements from both input sequences, excluding duplicates.
Thoughts on args.Parameters - I really do not know at this point other than to guess that it returns a collection of Parameters and removes duplicates?
Questions:
Could you help talk me through what is going on in args.Parameters?
logParameter Debugger Image
Nlog Database Table Image
LogService class
public class LogService : ILog
{
private readonly ILogger _log;
public LogService(Type type)
{
_log = LogManager.GetLogger(type.FullName);
}
public void Debug(string message, params object[] args)
{
Log(LogLevel.Debug, message, args);
}
public void Info(string message, params object[] args)
{
Log(LogLevel.Info, message, args);
}
public void Warn(string message, params object[] args)
{
Log(LogLevel.Warn, message, args);
}
public void Error(string message, params object[] args)
{
Log(LogLevel.Error, message, args);
}
public void Error(Exception ex)
{
Log(LogLevel.Error, null, null, ex);
}
public void Error(Exception ex, string message, params object[] args)
{
Log(LogLevel.Error, message, args, ex);
}
public void Fatal(Exception ex, string message, params object[] args)
{
Log(LogLevel.Fatal, message, args, ex);
}
private void Log(LogLevel level, string message, object[] args)
{
_log.Log(typeof(LogService), new LogEventInfo(level, _log.Name, null, message, args));
}
private void Log(LogLevel level, string message, object[] args, Exception ex)
{
_log.Log(typeof(LogService), new LogEventInfo(level, _log.Name, null, message, args, ex));
}
}
ILog interface
public interface ILog
{
void Debug(string message, params object[] args);
void Info(string message, params object[] args);
void Warn(string message, params object[] args);
void Error(string message, params object[] args);
void Error(Exception ex);
void Error(Exception ex, string message, params object[] args);
void Fatal(Exception ex, string message, params object[] args);
}
There's a lot to unpack here. You're not really asking for an answer to a specific question so much as a code walkthrough and explanation of an existing solution that works, so I might suggest posting to StackExchange Code Review if you need much more than what I'm going to give you here. Not trying to be unhelpful, but, like, if your question is, "Is my thinking right?" and the answer is "sort of," there's a lot of discussion on each individual point to explain why "sort of" is the answer (or "no," or "yes," as the case may be). It can turn into a lengthy answer, followed up by additional questions for clarification, which require yet additional answers... and StackOverflow isn't really a discussion forum capable of those sorts of things.
[i.e., I'll take probably an hour and write up an answer here... but I can't promise I'll actually be back to follow up on anything because there are other questions to answer and other things I need to allocate time to. StackOverflow is really more about "How do I...?" or other things that have a single, reasonably concrete answer.]
First, I recommend diving in yourself with a debugger on some breakpoints to actually see what's going on. For example, you asked what's in LimitType in one area - you could pretty easily answer that one by just sticking a breakpoint on that line and looking at the value. This will be a good way to follow up for additional clarification yourself - breakpoints for the win.
Second, I recommend spending some time with the Autofac docs. There's a lot of documentation out there that can answer questions.
The NLog module here appears to be based on the log4net module in the documentation which has a bit more explanation of what's going on.
There's an explanation of parameters (like TypedParameter) and how they're used.
Given the docs can round out some things that may not be clear, rather than try to address each "are my thoughts correct" item, let me just heavily annotate the module and hope that clarifies things.
// General module documentation is here:
// https://autofac.readthedocs.io/en/latest/configuration/modules.html
public class LoggingModule : Module
{
// Load basically registers types with the container just like
// if you were doing it yourself on the ContainerBuilder. It's
// just a nice way of packaging up a set of registrations so
// they're not all in your program's "Main" method or whatever.
protected override void Load(ContainerBuilder builder)
{
// This is a lambda registration. Docs here:
// https://autofac.readthedocs.io/en/latest/register/registration.html#lambda-expression-components
// This one uses both the component context (c) and the incoming
// set of parameters (p). In this lambda, the parameters are NOT the set of constructor
// parameters that Autofac has resolved - they're ONLY things that
// were MANUALLY specified. In this case, it's assuming a TypedParameter
// with a System.Type value is being provided manually. It's not going
// to try resolving that value from the container. This is going hand-in-hand
// with the logParameter you see in AttachToComponentRegistration.
// Parameter docs are here:
// https://autofac.readthedocs.io/en/latest/resolve/parameters.html
// In general if you resolve something that has both manually specified parameters
// and things that can be resolved by Autofac, the manually specified parameters
// will take precedence. However, in this lambda it's very specifically looking
// for a manually specified parameter.
// You'll want to keep this as a default InstancePerDependency because you probably
// want this to live as long as the thing using it and no longer. Likely
// NLog already has object pooling and caching built in so this isn't as
// expensive as you think, but I'm no NLog expert. log4net does handle
// that for you.
builder
.Register((c, p) => new LogService(p.TypedAs<Type>()))
.AsImplementedInterfaces();
}
// This method attaches a behavior (in this case, an event handler) to every
// component registered in the container. Think of it as a way to run a sort
// of "global foreach" over everything registered.
protected override void AttachToComponentRegistration(
IComponentRegistry componentRegistry,
IComponentRegistration registration)
{
// The Preparing event is called any time a new instance is needed. There
// are docs for the lifetime events but Preparing isn't on there. Here are the
// docs and the issue I filed on your behalf to get Preparing documented.
// https://autofac.readthedocs.io/en/latest/lifetime/events.html
// https://github.com/autofac/Documentation/issues/69
// You can see the Preparing event here:
// https://github.com/autofac/Autofac/blob/6dde84e5b0a3f82136a0567a84da498b04e1fa2d/src/Autofac/Core/IComponentRegistration.cs#L83
// and the event args here:
// https://github.com/autofac/Autofac/blob/6dde84e5b0/src/Autofac/Core/PreparingEventArgs.cs
registration.Preparing +=
(sender, args) =>
{
// The Component is the thing being resolved - the thing that
// needs a LogService injected. The Component.Activator is the
// thing that is actually going to execute to "new up" an instance
// of the Component. The Component.Activator.LimitType is the actual
// System.Type of the thing being resolved.
var forType = args.Component.Activator.LimitType;
// The docs above explain ResolvedParameter - basically a manually
// passed in parameter that can execute some logic to determine if
// it satisfies a constructor or property dependency. The point of
// this particular parameter is to provide an ILog to anything being
// resolved that happens to have an ILog constructor parameter.
var logParameter = new ResolvedParameter(
// p is the System.Reflection.ParameterInfo that describes the
// constructor parameter that needs injecting. c is the IComponentContext
// in which the resolution is being done (not used here). If this
// method evaluates to true then this parameter will be used; if not,
// it will refuse to provide a value. In this case, if the parameter
// being injected is an ILog, this ResolvedParameter will tell Autofac
// it can provide a value.
(p, c) => p.ParameterType == typeof(ILog),
// p and c are the same here, but this time they're used to actually
// generate the value of the parameter - the ILog instance that should
// be injected. Again, this will only run if the above predicate evaluates
// to true. This creates an ILog by manually resolving from the same
// component context (the same lifetime scope) as the thing that
// needs the ILog. Remember earlier that call to p.AsTyped<Type>()
// to get a parameter? The TypedParameter thing here is how that
// value gets poked in up there. This Resolve call will effectively
// end up calling the lambda registration.
(p, c) => c.Resolve<ILog>(TypedParameter.From(forType)));
// The thing being resolved (the component that consumes ILog) now
// needs to be told to make use of the log parameter, so add it into
// the list of parameters that can be used when resolving that thing.
// If there's an ILog, Autofac will use this specified parameter to
// fulfill the requirement.
args.Parameters = args.Parameters.Union(new[] { logParameter });
};
}
}
Something missing from this that's present in the log4net module example is the ability to do property injection for the logger. However, I'm not going to solve that here; you can look at the example right in the documentation and take that as an exercise to work on if you need that functionality.
I hope that helps. I'll probably not be coming back to follow up on additional questions, so if this isn't enough, I very, very much recommend setting some breakpoints, maybe setting up some tiny minimal-repro unit tests, that sort of thing, and do some deeper exploration to get clarity. Honestly, it's one thing to have someone else explain it, but it's another to actually see it in action and dive into the source of various projects. You'll come out with a fuller understanding with the latter approach, even if it's potentially not as fast.

Symfony2 - Forced to validate inside DataTransformer because of type hint on setter

I have created an Object to ID Data Transformer. It's part of a custom ObjectIdType that allows me to enter the ID of a document instead of using a 'document' form type. It's handy for MongoDB (when there could be 100 million documents to choose from).
The Data Transformer does a query upon the ID and returns an object. If it can't find an object then it returns null. The issue is - sometimes null is an acceptable value, and sometimes it isn't.
Even if I add a NotNull validator, I get the following error -
Catchable Fatal Error: Argument 1 passed to Character::setPlayer() must be an instance of Document\Player, null given
So it's calling the setter regardless of the validation failing. I fixed this by throwing a TransformationFailedException within the transformer - but this just seems like a bad idea. I shouldn't really be using a Data Transformer to validate.
The code of the transformer is below. What I'd like is to be able to put the validator in the correct place, and intercept the setter so it doesn't get called. Generally, this seems like a bit of a code smell, I would love to know how other people have solved this issue.
class ObjectToIdTransformer implements DataTransformerInterface
{
private $objectLocator;
private $objectName;
private $optional;
/**
* #param ObjectLocator $objectLocator
* #param $objectName
*/
public function __construct(ObjectLocator $objectLocator, $objectName, $optional = false)
{
$this->objectLocator = $objectLocator;
$this->objectName = $objectName;
$this->optional = $optional;
}
/**
* {#inheritdoc}
*/
public function transform($value)
{
if (null === $value) {
return null;
}
if (!$value instanceof BaseObject) {
throw new TransformationFailedException("transform() expects an instance of BaseObject.");
}
return $value->getId();
}
/**
* {#inheritdoc}
*/
public function reverseTransform($value)
{
if (null === $value) {
return null;
}
$repo = $this->objectLocator->getRepository($this->objectName);
$object = $repo->find($value);
if (!$this->optional && !$object) {
throw new TransformationFailedException("This is probably a bad place to validate data.");
}
return $object;
}
}
Actually, it's a PHP quirk that's very unintuitive — especially for those coming from other (logical, intuitive, sane) languages like Java. If you want to be able to pass a null argument to a typehinted parameter, you have to set its default value to null:
public function setPlayer(Player $player = null)
{
// ...
}
Yea, talk about some consistency here...

Can I use NUnit TestCase to test mocked repository and real repository

I would like to be able to run tests on my fake repository (that uses a list)
and my real repository (that uses a database) to make sure that both my mocked up version works as expected and my actual production repository works as expected. I thought the easiest way would be to use TestCase
private readonly StandardKernel _kernel = new StandardKernel();
private readonly IPersonRepository fakePersonRepository;
private readonly IPersonRepository realPersonRepository;
[Inject]
public PersonRepositoryTests()
{
realPersonRepository = _kernel.Get<IPersonRepository>();
_kernel = new StandardKernel(new TestModule());
fakePersonRepository = _kernel.Get<IPersonRepository>();
}
[TestCase(fakePersonRepository)]
[TestCase(realPersonRepository)]
public void CheckRepositoryIsEmptyOnStart(IPersonRepository personRepository)
{
if (personRepository == null)
{
throw new NullReferenceException("Person Repostory never Injected : is Null");
}
var records = personRepository.GetAllPeople();
Assert.AreEqual(0, records.Count());
}
but it asks for a constant expression.
Attributes are a compile-time decoration for an attribute, so anything that you put in a TestCase attribute has to be a constant that the compiler can resolve.
You can try something like this (untested):
[TestCase(typeof(FakePersonRespository))]
[TestCase(typeof(PersonRespository))]
public void CheckRepositoryIsEmptyOnStart(Type personRepoType)
{
// do some reflection based Activator.CreateInstance() stuff here
// to instantiate the incoming type
}
However, this gets a bit ugly because I imagine that your two different implementation might have different constructor arguments. Plus, you really don't want all that dynamic type instantiation code cluttering the test.
A possible solution might be something like this:
[TestCase("FakePersonRepository")]
[TestCase("TestPersonRepository")]
public void CheckRepositoryIsEmptyOnStart(string repoType)
{
// Write a helper class that accepts a string and returns a properly
// instantiated repo instance.
var repo = PersonRepoTestFactory.Create(repoType);
// your test here
}
Bottom line is, the test case attribute has to take a constant expression. But you can achieve the desired result by shoving the instantiation code into a factory.
You might look at the TestCaseSource attribute, though that may fail with the same error. Otherwise, you may have to settle for two separate tests, which both call a third method to handle all of the common test logic.