shibayan / Sharprompt

Interactive command-line based application framework for C#
MIT License
771 stars 50 forks source link

Feature/make lib unit testable #217

Closed MadL1me closed 2 years ago

MadL1me commented 2 years ago

Hi there. I was developing console app with your beatiful library and CliFx, and my app requires a lot of unit tests.

It wasn't before, but now, your library is unit testable, because you can Mock implementation for IPrompt interface. I added unit tests so you can see that in MockablePromptTests.cs. I tested all public methods, and they work perfectly fine. What I did, is basically made Prompt static class only as a wrapper for IPrompt interface, and moved all the logic to IPrompt realisation: DefualtPrompt class.

You can achive unit-testability now by replacing realisation of new Prompt property:

public static partial class Prompt
{
    public static IPrompt PromptRealisation { get; set; } = new DefaultPrompt(); 
}

So now you can make mock realisation like this for example:


var mock = new Mock<IPrompt>();

mock.Setup(p => p.Input<string>("What's your name?", 
    null, null, null)).Returns("MadL1me");

Prompt.PromptRealisation = mock.Object;

// Method now returnes our defined value above:
var value = Prompt.Input<string>("What is you name?")
value == "MadL1me" // true

I didn't changed any of your public api interface or code style, lib is still can be easy used with static class Prompt. But there a bonus, you are not required to use static class, now you can use Dependency Injection to Inject IPrompt interface realisation, and use it in code like this:

//old way (works with new way too)
string SomeMethod() 
{
    var name = Prompt.Input<string>("What's your name?");
    return name;
}

//new way 
string SomeMethod(IPrompt prompt)  // somewhere registered as singleton, this a nice little bonus
{
    var name = prompt.Input<string>("What's your name?");
    return name;
}

Hope you'll like it and add to main repository. Thanks for reading and your attention.

shibayan commented 2 years ago

I think that it is better to prepare our own interface for Mock on the application side, rather than adding the functionality for Mock to the library.

Although we plan to implement testable code for the Prompt implementation itself in v3, it appears that mocking the Prompt class will only increase complexity and provide no benefit.

MadL1me commented 2 years ago

I think that it is better to prepare our own interface for Mock on the application side, rather than adding the functionality for Mock to the library.

This sounds not so good, because you basically need to make entire facade-wrapper around Sharprompt just to make code testable.

Although we plan to implement testable code for the Prompt implementation itself in v3, it appears that mocking the Prompt class will only increase complexity and provide no benefit.

The only and main benefit is - making code using library testable. Every time you need to test specific case your app have, while using Sharprompt as UI, is impossible to do, because it will throw exception, it had in my app, so I forked and proposed this change I made so I can test my cli tool.

You are the owner, so your decision is a law, anyway, thank you very much for you time, will wait for v3

shibayan commented 2 years ago

Mocking of the Prompt class should be done by the user, and the library does not need to provide that functionality.

Just as the BCL Console class does not provide an interface for mocking, the Prompt class does not plan to provide such an interface.

However, in the future, we may provide another way to use the library that is not a static method. (e.g., by making the Form class directly available).