JasonBock / Rocks

A mocking library based on the Compiler APIs (Roslyn + Mocks)
MIT License
263 stars 20 forks source link

Set access modifier for generated `*Expectations` #341

Closed Prodigio closed 2 months ago

Prodigio commented 2 months ago

Is your feature request related to a problem? Please describe. I'm writing some unit tests with xUnit. To have them a bit more structured, I bundle method related unit tests in a sub class, within the main test class, like so:

[assembly: Rock(typeof(IBarManager), BuildType.Create)]

namspace ...

public class FooServiceTest
{
    public class FooTestBase
    {
        private readonly IBarManagerCreateExpectations _barManagerCreateExpectations;
        private readonly IFooService _fooService; // or sut (system under test)

        // Constructor
    }

    // GetItem is a method of IFooService
    public class GetItem : FooTestBase
    {
        [Fact]
        public void GetItem()
        {
            // test logic
            // Make use of _barManagerCreateExpectations here
        }
    }

    // GetDefinition is a method of IFooService
    public class GetDefinition : FooTestBase
    {
        [Fact]
        public void GetDefinition()
        {
            // test logic
            // Make use of _barManagerCreateExpectations here
        }
    }
}

Which does not work, because IBarManagerCreateExpectations is internal. Is there a reason, which I don't understand yet, that the generated classes are internal sealed, like seen below?

https://github.com/JasonBock/Rocks/blob/f85a77da22a5b06996af4c34169c167e5e3b1dfa/src/Rocks/Builders/Create/MockBuilder.cs#L16-L21

Describe the solution you'd like I'd like to have the option to set the access modifier for a class. For example when using the RockAttribute:

[assembly: Rock(typeof(IBarManager), BuildType.Create, Modifier.Public)]

Describe alternatives you've considered I can move away from using sub classes, sure.

Additional context none

JasonBock commented 2 months ago

Which does not work, because IBarManagerCreateExpectations is internal

But if all of this is within one project, this should be doable, as internal types are visible to everything within that project/assembly. Is your scenario one where multiple projects are in play and one is referencing the other?

Is there a reason, which I don't understand yet, that the generated classes are internal sealed

Mostly because the way I expected developers to use Rocks is in a unit test project that wasn't going to be referenced by another library. If you want another library to "see" the expectation type, you can add [InternalsVisibleTo] to the referenced library as a possible solution.

I'll consider this as a possible feature in the future. I have a concern that this may add complexity that might make things somewhat confusing. For example, if you have two projects, both of which reference Rocks and generate expectations for a type, there will be a naming collision if the referenced project specifies that the expectation should be public (or [InternalsVisibleTo] is used). I have code that will look for a possible collision and create a different name to avoid that scenario, but then you have a IBarCreateExpectations2 class, which....yes, kind of ugly, but it should be a rare situation. Maybe by having something like a Modifier enum would actually help.

Prodigio commented 2 months ago

Thanks for you quick response!

Well... it's late already. I just played around with public and protected access modifiers for the _barManagerCreateExpectations field. But it should be internal, of course. My bad!

Will close this issue. Maybe the idea with specifiying the access modifier is good anyway.

JasonBock commented 2 months ago

It's not a bad idea, but since you figured out what the real issue was, I'll leave this closed. Maybe in the future I'll consider it if there's a need for it.