There is official example how to create CustomAssertion at FluentAssertions docs, however my attempt to apply it fails. Here's the code:
public abstract class BaseTest
{
public List<int> TestList = new List<int>() { 1, 2, 3 };
}
public class Test : BaseTest { }
public class TestAssertions
{
private readonly BaseTest test;
public TestAssertions(BaseTest test)
{
this.test = test;
}
[CustomAssertion]
public void BeWorking(string because = "", params object[] becauseArgs)
{
foreach (int num in test.TestList)
{
num.Should().BeGreaterThan(0, because, becauseArgs);
}
}
}
public class CustomTest
{
[Fact]
public void TryMe()
{
Test test = new Test();
test.Should().BeWorking(); // error here
}
}
I'm getting compile error:
CS1061 'ObjectAssertions' does not contain a definition for 'BeWorking' and no accessible extension method 'BeWorking' accepting a first argument of type 'ObjectAssertions' could be found (are you missing a using directive or an assembly reference?)
I also tried to move BeWorking from TestAssertions to BaseTest but it still won't work. What am I missing and how do I make it work?
You did a very good job actually :)
The most important thing you are missing is the Extension class. I'll guide you through.
Add this class:
public static class TestAssertionExtensions
{
public static TestAssertions Should(this BaseTest instance)
{
return new TestAssertions(instance);
}
}
Fix your TestAssertions class like this:
public class TestAssertions : ReferenceTypeAssertions<BaseTest, TestAssertions>
{
public TestAssertions(BaseTest instance) => Subject = instance;
protected override string Identifier => "TestAssertion";
[CustomAssertion]
public AndConstraint<TestAssertions> BeWorking(string because = "", params object[] becauseArgs)
{
foreach (int num in Subject.TestList)
{
num.Should().BeGreaterThan(0, because, becauseArgs);
}
return new AndConstraint<TestAssertions>(this);
}
}
Your TryMe() test should be working fine now. Good luck.
Related
In this example, I want to patch PatchTarget.QSingleton\<T\>.get_Instance().
How to get it done with Harmony or MonoMod?
Harmony:
"Unhandled exception. System.NotSupportedException: Specified method
is not supported."
MonoMod:
"Unhandled exception. System.ArgumentException: The given generic
instantiation was invalid."
Code snippet: (runnable with dotnetfiddle.net)
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Reflection.Emit;
using HarmonyLib;
namespace PatchTarget {
public abstract class QSingleton<T> where T : QSingleton<T>, new() {
protected static T instance = null; protected QSingleton() { }
public static T Instance { get {
if (instance == null) {
instance = new T();
Console.Write($"{typeof(T).Name}.Instance: impl=QSingleton");
}
return instance;
} }
}
}
namespace Patch {
public class TypeHelper<T> where T : PatchTarget.QSingleton<T>, new() {
public static T InstanceHack() {
Console.Write($"{typeof(T).Name}.Instance: impl=InstanceHack");
return null;
}
}
public static class HarmonyPatch {
public static Harmony harmony = new Harmony("Try");
public static void init() {
var miOriginal = AccessTools.Property(typeof(PatchTarget.QSingleton<>), "Instance").GetMethod;
var miHack = AccessTools.Method(typeof(TypeHelper<>), "InstanceHack");
harmony.Patch(miOriginal, prefix: new HarmonyMethod(miHack));
}
}
public static class MonoModPatch {
public static MonoMod.RuntimeDetour.Detour sHook;
public static void init() {
var miOriginal = AccessTools.Property(typeof(PatchTarget.QSingleton<>), "Instance").GetMethod;
var miHack = AccessTools.Method(typeof(TypeHelper<>), "InstanceHack");
sHook = new MonoMod.RuntimeDetour.Detour(miOriginal, miHack);
}
}
}
class Program {
public static void Main() {
Patch.HarmonyPatch.init();
// Patch.MonoModPatch.init();
Console.WriteLine($"done");
}
}
After some trial and error, I got something working, but not the reason behind it.
Both Harmony and MonoMod.RuntimeDetour can hook with the typeof(QSingleton<SampleA>).GetMethod(), but not typeof(QSingleton<>).GetMethod().
Harmony output is unexpected.
Harmony attribute annotation doesn't seem to work.
Generating IL seems useless due to the potential lack of TypeSpec for generic.
Questions:
What is the difference between QSingleton<>.Instance and QSingleton<SampleA>.Instance in the sample?
I would guess that <>.Instance is MethodDef, while <SampleA>.Instance is TypeSpec.MemberRef.
Why does Harmony/MonoMod.RuntimeDetour need TypeSpec.MemberRef? For generating redirection stub?
Is it possible to fix the hook under Harmony?
Can Harmony/MonoMod generates "ldtoken <TypeSpec>" if TypeSpec already exists?
Can Harmony/MonoMod dynamically generates necessary TypeSpec for generics?
Code snippet: (runnable with dotnetfiddle.net)
using System;
using HarmonyLib;
namespace PatchTarget {
public abstract class QSingleton<T> where T : QSingleton<T>, new() {
protected static T instance = null; protected QSingleton() { }
public static T Instance { get {
if (instance == null) {
instance = new T();
Console.WriteLine($"{typeof(T).Name}.Instance: impl=QSingleton");
}
return instance;
} }
}
public class SampleA : QSingleton<SampleA> {
public SampleA() { Console.WriteLine("SampleA ctor"); }
}
public class SampleB : QSingleton<SampleB> {
public SampleB() { Console.WriteLine("SampleB ctor"); }
}
}
namespace Patch {
public class TypeHelper<T> where T : PatchTarget.QSingleton<T>, new() {
public static T InstanceHack() {
Console.WriteLine($"{typeof(T).Name}.Instance: impl=InstanceHack");
return null;
}
// For Harmony as Prefix, but attribute does not work.
public static bool InstanceHackPrefix(T __result) {
Console.WriteLine($"{typeof(T).Name}.Instance: impl=InstanceHack");
__result = null;
return false;
}
}
public static class HarmonyPatch {
public static Harmony harmony = new Harmony("Try");
public static void init() {
// Attribute does not work.
// Transpiler does not work because the lack of TypeSpec to setup generic parameters.
var miOriginal = AccessTools.Property(typeof(PatchTarget.QSingleton<PatchTarget.SampleB>), "Instance").GetMethod;
var miHack = AccessTools.Method(typeof(TypeHelper<PatchTarget.SampleB>), "InstanceHackPrefix");
harmony.Patch(miOriginal, prefix: new HarmonyMethod(miHack));
}
}
public static class MonoModPatch {
public static MonoMod.RuntimeDetour.Detour sHook;
public static void init() {
var miOriginal = AccessTools.Property(typeof(PatchTarget.QSingleton<PatchTarget.SampleB>), "Instance").GetMethod;
var miHack = AccessTools.Method(typeof(TypeHelper<PatchTarget.SampleB>), "InstanceHack");
sHook = new MonoMod.RuntimeDetour.Detour(miOriginal, miHack);
}
}
}
class Program {
public static void Main() {
_ = PatchTarget.SampleA.Instance;
// MonoMod works (replaces globally).
// Harmony hooks, but in an expected way (T becomes SampleB, not 1st generic type parameter).
// try { Patch.HarmonyPatch.init(); } catch (Exception e) { Console.WriteLine($"Harmony error: {e.ToString()}"); }
try { Patch.MonoModPatch.init(); } catch (Exception e) { Console.WriteLine($"MonoMod error: {e.ToString()}"); }
_ = PatchTarget.SampleB.Instance;
_ = PatchTarget.SampleA.Instance;
Console.WriteLine($"done");
}
}
MonoMod.RuntimeDetour Output:(Work as intended)
SampleA.Instance: impl=QSingleton
SampleB.Instance: impl=InstanceHack
SampleA.Instance: impl=InstanceHack
Harmony Output:(Broken <T>)
SampleA.Instance: impl=QSingleton
SampleB.Instance: impl=InstanceHack
SampleB.Instance: impl=InstanceHack
using System;
namespace AssemblyOne
{
public class AssemblyOneClassOne
{
protected internal int ID = 101;
public int id = 102;
public void Print()
{
Console.WriteLine("Abdullah is a handsome hunk!");
}
}
public class AssemblyOneClassTwo
{
public void SampleMethod()
{
AssemblyOneClassOne a1 = new AssemblyOneClassOne();
Console.WriteLine(a1.ID);
}
}
public class A
{
public static void Main()
{
AssemblyOneClassTwo a2 = new AssemblyOneClassTwo();
a2.SampleMethod();
Console.ReadKey();
}
}
}
using System;
using AssemblyOne;
namespace AssemblyTwo
{
public class AssemblyTwoClassOne
{
AssemblyOneClassOne instance = new AssemblyOneClassOne();
instance.Print();//Over here I am getting compile time error, 'instance' does not exist in the current context, 'instance.Print' does not exist in the current context
}
}
As far as I know, public types can be accessed in anywhere in the same assembly as well as in another assembly
Yes, public types can be accessed from another assembly but your method Print() should be called inside a method of class AssemblyTwoClassOne.
Something like this -
namespace AssemblyTwo
{
public class AssemblyTwoClassOne
{
AssemblyOneClassOne instance = new();
public void Method() => instance.Print();
}
}
What it boils down to is that I'm trying to make a Generic and while the type shows up correctly at runtime, during compile time its still object, and so I cannot use any of the generic type's methods.
thanks to brainless coder on a previous question I'm able to move forward a bit
dotnetfiddle
using System;
using System.Collections.Generic;
using System.Linq;
public class Program
{
public static void Main()
{
var sample = new Baz<List<Foo>>();
sample.DoSomething();
}
public class Foo
{
}
public class Bar<T>
{
public void Boom()
{
}
}
public class Baz<T>
{
public void DoSomething(){
if (typeof(T).Name == "List`1")
{
var typeName = typeof(T).GetGenericArguments().Single().FullName;
var type = Type.GetType(typeName);
var genericRepoType = typeof(Bar<>);
var specificRepoType = genericRepoType.MakeGenericType(new Type[] { type });
var genericBar = Activator.CreateInstance(specificRepoType);
Console.WriteLine(genericBar.GetType().Name); // Shows Bar`1
// but at compile time its foo is still an object
genericBar.Boom();
//will error with 'object' does not contain a definition for Boom
}
}
}
}
This sounds like a very questionable design, but if you must, dynamic neatly solves your problem.
public static void Main() {
var sample = new Baz<List<Foo>>();
sample.DoSomething();
}
public class Foo { }
public class Bar<T> {
public void Boom() {
Console.WriteLine("I am booming");
}
}
public class Baz<T> {
public void DoSomething() {
var typeName = typeof(T).GetGenericArguments().Single().FullName;
var type = Type.GetType(typeName);
var genericRepoType = typeof(Bar<>);
var specificRepoType = genericRepoType.MakeGenericType(new Type[] { type });
dynamic genericBar = Activator.CreateInstance(specificRepoType);
Console.WriteLine(genericBar.GetType().Name);
genericBar.Boom();
}
}
https://dotnetfiddle.net/uPpfJa
Alternatively, you could declare an IBar interface.
public class Bar<T> : IBar {
public void Boom() {
Console.WriteLine("I am booming");
}
}
interface IBar {
void Boom();
}
...
var genericBar = (IBar)Activator.CreateInstance(specificRepoType);
I have the following classes
public class A
{
protected static Dictionary<string,Func<BaseClass>> dict = new Dictionary<string,Func<BaseClass>>();
public static void AddGenerator(string type,Func<BaseClass> fncCreateObject)
{
dict.Add(type,fncCreateObject);
}
}
class B : BaseClass
{
static B()
{
A.AddGenerator("b",CreateObject);
}
protected B()
{}
pulic static B CreateObject()
{
return new B();
}
}
NOTE: The above code is simply an example but very closely relates to the what I'm trying to achieve.
Many people would advice using an IoC container such as NInject or Unity but my main reason for this post if to figure out why the above code does not execute as it is expected to.
So, in the above code, I'm expecting class B's static constructor to call on the static method of class A and an entry should be available in the dictionary for the rest of the application life cycle.
However, when I run the code and debug, I found that the dictionary is empty.
Why is the code invoked from class B's static constructor not executing?
From the documentation:
A static constructor is called automatically to initialize the class before the first instance is created or any static members are referenced.
Clearly, at the point in your code where you inspect the dictionary, no instance has yet been created, and no static members have been referenced.
Not exactly a 1:1 translation, of your sample into MEF, but it should give you a good idea what MEF is capable of:
using System;
using System.Collections.Generic;
namespace ConsoleApplication4
{
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
using System.Reflection;
class Program
{
static void Main(string[] args)
{
var assemblyCatalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());
var directoryCatalog = new DirectoryCatalog(".");
var compositeCatalog = new AggregateCatalog(assemblyCatalog, directoryCatalog);
var container = new CompositionContainer(compositeCatalog);
var a = A.Instance;
container.SatisfyImportsOnce(a);
a.PrintCatalog();
}
}
public sealed class A
{
private static readonly A instance = new A();
static A() { }
private A() { }
public static A Instance { get { return instance; } }
[ImportMany]
private List<IBType> BTypes;
public void PrintCatalog()
{
foreach (var bType in BTypes)
{
Console.WriteLine(bType.GetType());
}
}
}
[Export(typeof(IBType))]
class B:IBType
{
static B()
{
}
protected B()
{}
public void DoSomething() { }
}
[Export(typeof(IBType))]
class B2:IBType
{
static B2()
{
}
protected B2()
{}
public void DoSomething() { }
}
interface IBType
{
void DoSomething();
}
}
I've also included the safest implementation of a Singleton pattern known to me. MEF will allow you to source many implementations of the same interface which are resolved dynamically at runtime. I used it also with metadata attributes, like version and name.
But if you need it to work with a base abstract class, check out this article.
The same code as above, but with metadata attributes use sample:
using System;
using System.Collections.Generic;
namespace ConsoleApplication4
{
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
using System.Linq;
using System.Reflection;
class Program
{
static void Main(string[] args)
{
var assemblyCatalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());
var directoryCatalog = new DirectoryCatalog(".");
var compositeCatalog = new AggregateCatalog(assemblyCatalog, directoryCatalog);
var container = new CompositionContainer(compositeCatalog);
var a = A.Instance;
container.SatisfyImportsOnce(a);
a.PrintCatalog();
a.BTypes.Single(s=>s.Metadata.Name.Equals("Second")).Value.DoSomething();
}
}
public sealed class A
{
private static readonly A instance = new A();
static A() { }
private A() { }
public static A Instance { get { return instance; } }
[ImportMany]
public List<Lazy<IBType,IBTypeMetadata>> BTypes;
public void PrintCatalog()
{
foreach (var bType in BTypes)
{
Console.WriteLine(bType.Value.GetType());
}
}
}
[Export(typeof(IBType))]
[BTypeMetadata("First")]
class B:IBType
{
static B()
{
}
protected B()
{}
public void DoSomething() { }
}
[Export(typeof(IBType))]
[BTypeMetadata("Second")]
class B2 : IBType
{
static B2()
{
}
protected B2()
{}
public void DoSomething()
{
Console.WriteLine("Hello from Second");
}
}
public interface IBType
{
void DoSomething();
}
public interface IBTypeMetadata
{
string Name { get; }
}
[MetadataAttribute]
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public class BTypeMetadataAttribute : ExportAttribute
{
public string Name { get; set; }
public BTypeMetadataAttribute(string name)
: base(typeof(IBTypeMetadata)) { Name = name; }
}
}
IMHO, MEF might help you as long as your plan is to call some public methods from a particular instance of any of the B-types. In your sample, you simply create new instances of a B-type, and I think there is more to it than what your sample shows.
MEF will create catalogs for you from your currently loaded assembly, as well as any number of assemblies from any number of directories. You can even have it dynamically re-composable, meaning, at runtime, you could potentially retrieve a DLL from a server, and have it added to your catalog without shutting down the application.
MEF is also hierarchical, so your B-types can have their own "catalogs". And to wire it all up, all you have to do is to call SatifyImportsOnce passing an instance of class A.
I keep getting the following error with the following code:
Error: "No exports were found that match the constraint:
ContractName MefTestSample.Contracts.ICanDoSomethingImportant"
Program.cs is as follows:
namespace MefTestSample
{
class Program
{
private static CompositionContainer m_Container;
static void Main(string[] args)
{
UseMockedUpTypes();
ICanDoSomethingImportant cool = m_Container.GetExport<ICanDoSomethingImportant>().Value;
cool.DoSomethingClever();
Console.ReadLine();
}
private static void UseMockedUpTypes()
{
//The commented out section works just by itself.
/*
m_Container =
new CompositionContainer(
new AssemblyCatalog(
typeof(MefTestSample.Mocks.ClassesDoNotNecessarly).Assembly));
*/
//This fails if i dont comment out the [ImportingConstructor] block.
var assemblyCatalog1 = new AssemblyCatalog(typeof (MefTestSample.Mocks.ClassesDoNotNecessarly).Assembly);
var myassembly = new AssemblyCatalog(typeof (ServiceLibrary.MoonService).Assembly);
var aggregateCatalog = new AggregateCatalog(assemblyCatalog1, myassembly);
m_Container = new CompositionContainer(aggregateCatalog);
}
}
}
Below is the code for ClassesDoNotNecessarly:
namespace MefTestSample.Mocks
{
[Export(typeof(ICanDoSomethingImportant))]
public class ClassesDoNotNecessarly : ICanDoSomethingImportant
{
//private IServicesContract _isc;
#region ICanDoSomethingImportant Members
/* This seems to be causing the problem.
[ImportingConstructor]
public ClassesDoNotNecessarly([Import("Moon")]IServicesContract isc)
{
string temp = isc.DisplayMessage();
Console.WriteLine(temp);
}
*/
public void DoSomethingClever()
{
Console.WriteLine("Hehe, I'm actually procrastinating!");
}
#endregion
}
}
MoonService is as follows:
namespace ServiceLibrary
{
[Export(typeof(IServicesContract))]
public class MoonService : IServicesContract
{
public string DisplayMessage()
{
return "Moon services were accessed.";
}
}
}
What i believe the problem is. If i leave program.cs as it is, and comment out the [ImportingConstructor] attribute + constructor in the ClassesDoNotNecessarly class, the program will display the text in that class.
If i uncomment the [ImportingConstructor] attribute n constructor, i then start getting the error shown at the top of this question.
Any ideas would be appreciated.
Remove the [Import("Moon")] in the constructor, the Export is not named, so the Import cannot be named in return.