{"action":"created","issue":{"url":"https://api.github.com/repos/dennisdoomen/fluentassertions/issues/271","labels_url":"https://api.github.com/repos/dennisdoomen/fluentassertions/issues/271/labels{/name}","comments_url":"https://api.github.com/repos/dennisdoomen/fluentassertions/issues/271/comments","events_url":"https://api.github.com/repos/dennisdoomen/fluentassertions/issues/271/events","html_url":"https://github.com/dennisdoomen/fluentassertions/issues/271","id":81224422,"number":271,"title":"Assertions to verify assembly and type structure","user":{"login":"piotrpMSFT","id":2379589,"avatar_url":"https://avatars.githubusercontent.com/u/2379589?v=3","gravatar_id":"","url":"https://api.github.com/users/piotrpMSFT","html_url":"https://github.com/piotrpMSFT","followers_url":"https://api.github.com/users/piotrpMSFT/followers","following_url":"https://api.github.com/users/piotrpMSFT/following{/other_user}","gists_url":"https://api.github.com/users/piotrpMSFT/gists{/gist_id}","starred_url":"https://api.github.com/users/piotrpMSFT/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/piotrpMSFT/subscriptions","organizations_url":"https://api.github.com/users/piotrpMSFT/orgs","repos_url":"https://api.github.com/users/piotrpMSFT/repos","events_url":"https://api.github.com/users/piotrpMSFT/events{/privacy}","received_events_url":"https://api.github.com/users/piotrpMSFT/received_events","type":"User","site_admin":false},"labels":[],"state":"open","locked":false,"assignee":null,"milestone":null,"comments":5,"created_at":"2015-05-27T01:54:43Z","updated_at":"2015-05-27T05:25:15Z","closed_at":null,"body":"I would like to expand the capabilities of the existing reflection assertions [Assembly, Type, MethodInfo, PropertyInfo] and add a few related assertions [ConstructorInfo, MemberInfo, MethodBase] to enable reasoning about the structure of assemblies and the types they contain.\r\n\r\n**AssemblyAssertions**\r\nAssemblyAssertions should expose the ability to assert that an assembly exposes a particular type:\r\n\r\n```csharp\r\nthis.GetType().Assembly\r\n    .Should().DefineType(\"MyNamespace\", \"MyTestClass\")\r\n         .Which.Should().BeDecoratedWith<TestClassAttribute>();\r\n```\r\n\r\n**TypeAssertions**\r\nTypeAssertions should allow us to assert that the type has expected members, does not have other members, is derived from a base class, and implements some interfaces:\r\n\r\n```csharp\r\ntypeof(MyClass)\r\n    .Should().Implement(typeof(IEnumerable), \"Because I like those linq extension methods\")\r\n       .And().NotImplement(typeof(IOrderedEnumerable), \"Because I intentionally don't guarantee order\")\r\n       .And().BeDerivedFrom(typeof(MyBaseClass), \"Because I'm a specialization of that class\")\r\n       .And().HaveIndexer(new [] { typeof(int) }, \"Because I'm a collection of some sort\")\r\n       .And().NotHaveIndexer(new [] { typeof(string) }, \"Because that would be inefficient\")\r\n       .And().HaveProperty(typeof(string), \"MyStringProperty\", \"Because my users expect it\")\r\n       .And().NotHaveProperty(typeof(string), \"YourStringProperty\", \"Because it belongs on a different class\")\r\n       .And().HaveMethod(typeof(ICanJump), \"Jump\", new [] { typeof(long) }, \"Because how high?\")\r\n       .And().NotHaveMethod(typeof(ICanJump), \"Jump\", new [] { typeof(string) }, \"Because AtoI is not our concern here\")\r\n       .And().HaveExplicitProperty(typeof(IHaveAName), \"Name\", \"Because properties are sometimes implemented explicitly to avoid name collisions\")\r\n       .And().NotHaveExplicitProperty(typeof(IHaveAName), \"LastName\", \"Because this property should have a unified implicit/explicit implementation\")\r\n       .And().HaveExplicitMethod(typeof(ICanJump), \"Jump\", new [] { typeof(long) })\r\n       .And().NotHaveExplicitMethod(typeof(ICanJump), \"Jump\", new [] { typeof(string) })\r\n       .And().HaveConstructor(new [] { typeof(IMyRepository) }, \"Because it allows dependency injection\")\r\n       .And().HaveDefaultConstructor(\"Because I want to verify...\")\r\n           Which.Should().HaveAccessModifier(CSharpAccessModifiers.Private, \"Because I don't want to be created without a repository!\");\r\n```\r\n\r\n**MethodBaseAssertions**\r\nBy exposing MethodBaseAssertions seperately from MethodInfo we can share code that applies to other implementations of MethodBase like ConstructorInfo.\r\n\r\n```csharp\r\nmyMethodBase\r\n    .Should().Return(typeof(int), \"Because that's this overload's job\")\r\n       .And().ReturnVoid(\"Because contradictions are fun\")\r\n       .And().HaveAccessModifier(CSharpAccessModifier.Internal);\r\n```\r\n\r\n**MethodInfoAssertions**\r\nThese retain their previous characteristics but can now also check for Async!\r\n```csharp\r\nmyMethodInfo\r\n    .Should().BeAsync(\"Because it's long running.\");\r\n```\r\n\r\n**PropertyInfoAssertions**\r\nThese are analagous to what we can do with Methods.\r\n```csharp\r\nmyPropertyInfo\r\n    .Should().BeWritable(\"Because it should have a setter, though I don't care about its accessability\")\r\n       .And().BeWritable(CSharpAccessModifier.ProtectedInternal, \"Because I do care\")\r\n       .And().BeReadable()\r\n       .And().BeReadable(CSharpAccessModifier.Public)\r\n       .And().NotBeReadable()\r\n       .And().NotBeWritable()\r\n       .And().Return(typeof(MyReturnType))\r\n```\r\n\r\nI am preparing a PullRequest to add these capabilities to the FluentAssertions project and am creating this issue to request approval for adding these capabilities and to have a discussion around the shape of the new assertions. I want to make sure this makes sense to the project overall before asking folks to review code."},"comment":{"url":"https://api.github.com/repos/dennisdoomen/fluentassertions/issues/comments/105762024","html_url":"https://github.com/dennisdoomen/fluentassertions/issues/271#issuecomment-105762024","issue_url":"https://api.github.com/repos/dennisdoomen/fluentassertions/issues/271","id":105762024,"user":{"login":"dennisdoomen","id":572734,"avatar_url":"https://avatars.githubusercontent.com/u/572734?v=3","gravatar_id":"","url":"https://api.github.com/users/dennisdoomen","html_url":"https://github.com/dennisdoomen","followers_url":"https://api.github.com/users/dennisdoomen/followers","following_url":"https://api.github.com/users/dennisdoomen/following{/other_user}","gists_url":"https://api.github.com/users/dennisdoomen/gists{/gist_id}","starred_url":"https://api.github.com/users/dennisdoomen/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/dennisdoomen/subscriptions","organizations_url":"https://api.github.com/users/dennisdoomen/orgs","repos_url":"https://api.github.com/users/dennisdoomen/repos","events_url":"https://api.github.com/users/dennisdoomen/events{/privacy}","received_events_url":"https://api.github.com/users/dennisdoomen/received_events","type":"User","site_admin":false},"created_at":"2015-05-27T05:25:14Z","updated_at":"2015-05-27T05:25:14Z","body":"In most cases where I had to deal with that, the generic version will call the non-generic version. So from that perspective I would say your approach is fine."}}