How do I unit test this promptui package written in golang?












1















I am new to golang and I am using an interactive prompt called promptui (https://github.com/manifoldco/promptui) in a project of mine. I have written several unit tests for this project already but I am struggling with how I would unit test this particular package that requires an input.



For example, How would I go about testing the following lines of code (encapsulated in a function):



func setEmail() string {
prompt := promptui.Prompt{Label: "Input your Email",
Validate: emailValidations,
}

email, err := prompt.Run()
if err != nil {
color.red("failed getting email")
os.exit(3)
}
return email
}


I think I need to somehow mock stdin but can't figure out the best way to do that within a test.










share|improve this question


















  • 1





    Why would you unit test an external package? It should have its own tests already.

    – Adrian
    Nov 14 '18 at 18:32






  • 1





    Well I want to test my function that uses that package.

    – Steve
    Nov 14 '18 at 18:34











  • Your function that uses it doesn't do anything itself, unit testing it delivers no value.

    – Adrian
    Nov 14 '18 at 18:48











  • It returns the email string. The function is used a few times throughout. I guess I could just reuse the same promptui code block throughout but thought that would go against DRY best practices.

    – Steve
    Nov 14 '18 at 18:58











  • I agree that there is a value in testing code that calls promptui. Tests create a safety net that reduce regression errors. You never know how you have to refactor/extend that code in the future.

    – Dmitry Harnitski
    Nov 14 '18 at 20:13
















1















I am new to golang and I am using an interactive prompt called promptui (https://github.com/manifoldco/promptui) in a project of mine. I have written several unit tests for this project already but I am struggling with how I would unit test this particular package that requires an input.



For example, How would I go about testing the following lines of code (encapsulated in a function):



func setEmail() string {
prompt := promptui.Prompt{Label: "Input your Email",
Validate: emailValidations,
}

email, err := prompt.Run()
if err != nil {
color.red("failed getting email")
os.exit(3)
}
return email
}


I think I need to somehow mock stdin but can't figure out the best way to do that within a test.










share|improve this question


















  • 1





    Why would you unit test an external package? It should have its own tests already.

    – Adrian
    Nov 14 '18 at 18:32






  • 1





    Well I want to test my function that uses that package.

    – Steve
    Nov 14 '18 at 18:34











  • Your function that uses it doesn't do anything itself, unit testing it delivers no value.

    – Adrian
    Nov 14 '18 at 18:48











  • It returns the email string. The function is used a few times throughout. I guess I could just reuse the same promptui code block throughout but thought that would go against DRY best practices.

    – Steve
    Nov 14 '18 at 18:58











  • I agree that there is a value in testing code that calls promptui. Tests create a safety net that reduce regression errors. You never know how you have to refactor/extend that code in the future.

    – Dmitry Harnitski
    Nov 14 '18 at 20:13














1












1








1








I am new to golang and I am using an interactive prompt called promptui (https://github.com/manifoldco/promptui) in a project of mine. I have written several unit tests for this project already but I am struggling with how I would unit test this particular package that requires an input.



For example, How would I go about testing the following lines of code (encapsulated in a function):



func setEmail() string {
prompt := promptui.Prompt{Label: "Input your Email",
Validate: emailValidations,
}

email, err := prompt.Run()
if err != nil {
color.red("failed getting email")
os.exit(3)
}
return email
}


I think I need to somehow mock stdin but can't figure out the best way to do that within a test.










share|improve this question














I am new to golang and I am using an interactive prompt called promptui (https://github.com/manifoldco/promptui) in a project of mine. I have written several unit tests for this project already but I am struggling with how I would unit test this particular package that requires an input.



For example, How would I go about testing the following lines of code (encapsulated in a function):



func setEmail() string {
prompt := promptui.Prompt{Label: "Input your Email",
Validate: emailValidations,
}

email, err := prompt.Run()
if err != nil {
color.red("failed getting email")
os.exit(3)
}
return email
}


I think I need to somehow mock stdin but can't figure out the best way to do that within a test.







unit-testing go command-prompt






share|improve this question













share|improve this question











share|improve this question




share|improve this question










asked Nov 14 '18 at 18:14









SteveSteve

197




197








  • 1





    Why would you unit test an external package? It should have its own tests already.

    – Adrian
    Nov 14 '18 at 18:32






  • 1





    Well I want to test my function that uses that package.

    – Steve
    Nov 14 '18 at 18:34











  • Your function that uses it doesn't do anything itself, unit testing it delivers no value.

    – Adrian
    Nov 14 '18 at 18:48











  • It returns the email string. The function is used a few times throughout. I guess I could just reuse the same promptui code block throughout but thought that would go against DRY best practices.

    – Steve
    Nov 14 '18 at 18:58











  • I agree that there is a value in testing code that calls promptui. Tests create a safety net that reduce regression errors. You never know how you have to refactor/extend that code in the future.

    – Dmitry Harnitski
    Nov 14 '18 at 20:13














  • 1





    Why would you unit test an external package? It should have its own tests already.

    – Adrian
    Nov 14 '18 at 18:32






  • 1





    Well I want to test my function that uses that package.

    – Steve
    Nov 14 '18 at 18:34











  • Your function that uses it doesn't do anything itself, unit testing it delivers no value.

    – Adrian
    Nov 14 '18 at 18:48











  • It returns the email string. The function is used a few times throughout. I guess I could just reuse the same promptui code block throughout but thought that would go against DRY best practices.

    – Steve
    Nov 14 '18 at 18:58











  • I agree that there is a value in testing code that calls promptui. Tests create a safety net that reduce regression errors. You never know how you have to refactor/extend that code in the future.

    – Dmitry Harnitski
    Nov 14 '18 at 20:13








1




1





Why would you unit test an external package? It should have its own tests already.

– Adrian
Nov 14 '18 at 18:32





Why would you unit test an external package? It should have its own tests already.

– Adrian
Nov 14 '18 at 18:32




1




1





Well I want to test my function that uses that package.

– Steve
Nov 14 '18 at 18:34





Well I want to test my function that uses that package.

– Steve
Nov 14 '18 at 18:34













Your function that uses it doesn't do anything itself, unit testing it delivers no value.

– Adrian
Nov 14 '18 at 18:48





Your function that uses it doesn't do anything itself, unit testing it delivers no value.

– Adrian
Nov 14 '18 at 18:48













It returns the email string. The function is used a few times throughout. I guess I could just reuse the same promptui code block throughout but thought that would go against DRY best practices.

– Steve
Nov 14 '18 at 18:58





It returns the email string. The function is used a few times throughout. I guess I could just reuse the same promptui code block throughout but thought that would go against DRY best practices.

– Steve
Nov 14 '18 at 18:58













I agree that there is a value in testing code that calls promptui. Tests create a safety net that reduce regression errors. You never know how you have to refactor/extend that code in the future.

– Dmitry Harnitski
Nov 14 '18 at 20:13





I agree that there is a value in testing code that calls promptui. Tests create a safety net that reduce regression errors. You never know how you have to refactor/extend that code in the future.

– Dmitry Harnitski
Nov 14 '18 at 20:13












2 Answers
2






active

oldest

votes


















2














You should not try to test promptui as it is expected to be tested by its author.



What you can test:




  1. You send correct parameters when you create promptui.Prompt

  2. You use that promptui.Prompt in your code

  3. You properly handle promptui.Prompt results


As you can see, all these tests does not verify if promptui.Prompt works correctly inside.



Tests #2 and #3 could be combined. You need to run you code against mock and if you got correct result, you can believe that both #2 and #3 are correct.



Create mock:



type Runner interface {
Run() (int, string, error)
}

type promptMock struct {
// t is not required for this test, but it is would be helpful to assert input parameters if we have it in Run()
t *testing.T
}

func (p promptMock) Run() (int, string, error) {
// return expected result
return 1, "", nil
}


You will need separate mock for testing error flow.



Update your code to inject mock:



func setEmail(runner Runner) string {
email, err := runner.Run()
if err != nil {
color.red("failed getting email")
os.exit(3)
}
return email
}


Now it is testable.



Create function that creates prompt:



func getRunner() promptui.Prompt {
return promptui.Prompt{Label: "Input your Email",
Validate: emailValidations,
}
}


Write simple assert test to verify that we create correct structure.



The only not tested line will be setEmail(getRunner()) but it is trivial and can be covered by other types of tests.






share|improve this answer
























  • Thank you for your help.

    – Steve
    Nov 14 '18 at 19:02



















1














For whatever reason, they don't export their stdin interface (https://github.com/manifoldco/promptui/blob/master/prompt.go#L49), so you can't mock it out, but you can directly mock os.Stdin and prefill it with whatever you need for testing. Though I agree with @Adrian, it has its own tests, so this shouldn't be necessary.



Extracted and refactored/simplified from source: Fill os.Stdin for function that reads from it



Refactored this way, it can be used for any function that reads from os.Stdin and expects a specific string.



Playground link: https://play.golang.org/p/rjgcGIaftBK



func TestSetEmail(t *testing.T) {
if err := TestExpectedStdinFunc("email@test.com", setEmail); err != nil {
t.Error(err)
return
}
fmt.Println("success")
}

func TestExpectedStdinFunc(expected string, f func() string) error {
content := byte(expected)
tmpfile, err := ioutil.TempFile("", "example")
if err != nil {
return err
}

defer os.Remove(tmpfile.Name()) // clean up

if _, err := tmpfile.Write(content); err != nil {
return err
}

if _, err := tmpfile.Seek(0, 0); err != nil {
return err
}

oldStdin := os.Stdin
defer func() { os.Stdin = oldStdin }() // Restore original Stdin

os.Stdin = tmpfile
actual := f()
if actual != expected {
return errors.New(fmt.Sprintf("test failed, exptected: %s actual: %s", expected, actual))
}

if err := tmpfile.Close(); err != nil {
return err
}
return nil
}





share|improve this answer


























  • Thanks for your help.

    – Steve
    Nov 14 '18 at 18:53











Your Answer






StackExchange.ifUsing("editor", function () {
StackExchange.using("externalEditor", function () {
StackExchange.using("snippets", function () {
StackExchange.snippets.init();
});
});
}, "code-snippets");

StackExchange.ready(function() {
var channelOptions = {
tags: "".split(" "),
id: "1"
};
initTagRenderer("".split(" "), "".split(" "), channelOptions);

StackExchange.using("externalEditor", function() {
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled) {
StackExchange.using("snippets", function() {
createEditor();
});
}
else {
createEditor();
}
});

function createEditor() {
StackExchange.prepareEditor({
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: true,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: 10,
bindNavPrevention: true,
postfix: "",
imageUploader: {
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
},
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
});


}
});














draft saved

draft discarded


















StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53306447%2fhow-do-i-unit-test-this-promptui-package-written-in-golang%23new-answer', 'question_page');
}
);

Post as a guest















Required, but never shown

























2 Answers
2






active

oldest

votes








2 Answers
2






active

oldest

votes









active

oldest

votes






active

oldest

votes









2














You should not try to test promptui as it is expected to be tested by its author.



What you can test:




  1. You send correct parameters when you create promptui.Prompt

  2. You use that promptui.Prompt in your code

  3. You properly handle promptui.Prompt results


As you can see, all these tests does not verify if promptui.Prompt works correctly inside.



Tests #2 and #3 could be combined. You need to run you code against mock and if you got correct result, you can believe that both #2 and #3 are correct.



Create mock:



type Runner interface {
Run() (int, string, error)
}

type promptMock struct {
// t is not required for this test, but it is would be helpful to assert input parameters if we have it in Run()
t *testing.T
}

func (p promptMock) Run() (int, string, error) {
// return expected result
return 1, "", nil
}


You will need separate mock for testing error flow.



Update your code to inject mock:



func setEmail(runner Runner) string {
email, err := runner.Run()
if err != nil {
color.red("failed getting email")
os.exit(3)
}
return email
}


Now it is testable.



Create function that creates prompt:



func getRunner() promptui.Prompt {
return promptui.Prompt{Label: "Input your Email",
Validate: emailValidations,
}
}


Write simple assert test to verify that we create correct structure.



The only not tested line will be setEmail(getRunner()) but it is trivial and can be covered by other types of tests.






share|improve this answer
























  • Thank you for your help.

    – Steve
    Nov 14 '18 at 19:02
















2














You should not try to test promptui as it is expected to be tested by its author.



What you can test:




  1. You send correct parameters when you create promptui.Prompt

  2. You use that promptui.Prompt in your code

  3. You properly handle promptui.Prompt results


As you can see, all these tests does not verify if promptui.Prompt works correctly inside.



Tests #2 and #3 could be combined. You need to run you code against mock and if you got correct result, you can believe that both #2 and #3 are correct.



Create mock:



type Runner interface {
Run() (int, string, error)
}

type promptMock struct {
// t is not required for this test, but it is would be helpful to assert input parameters if we have it in Run()
t *testing.T
}

func (p promptMock) Run() (int, string, error) {
// return expected result
return 1, "", nil
}


You will need separate mock for testing error flow.



Update your code to inject mock:



func setEmail(runner Runner) string {
email, err := runner.Run()
if err != nil {
color.red("failed getting email")
os.exit(3)
}
return email
}


Now it is testable.



Create function that creates prompt:



func getRunner() promptui.Prompt {
return promptui.Prompt{Label: "Input your Email",
Validate: emailValidations,
}
}


Write simple assert test to verify that we create correct structure.



The only not tested line will be setEmail(getRunner()) but it is trivial and can be covered by other types of tests.






share|improve this answer
























  • Thank you for your help.

    – Steve
    Nov 14 '18 at 19:02














2












2








2







You should not try to test promptui as it is expected to be tested by its author.



What you can test:




  1. You send correct parameters when you create promptui.Prompt

  2. You use that promptui.Prompt in your code

  3. You properly handle promptui.Prompt results


As you can see, all these tests does not verify if promptui.Prompt works correctly inside.



Tests #2 and #3 could be combined. You need to run you code against mock and if you got correct result, you can believe that both #2 and #3 are correct.



Create mock:



type Runner interface {
Run() (int, string, error)
}

type promptMock struct {
// t is not required for this test, but it is would be helpful to assert input parameters if we have it in Run()
t *testing.T
}

func (p promptMock) Run() (int, string, error) {
// return expected result
return 1, "", nil
}


You will need separate mock for testing error flow.



Update your code to inject mock:



func setEmail(runner Runner) string {
email, err := runner.Run()
if err != nil {
color.red("failed getting email")
os.exit(3)
}
return email
}


Now it is testable.



Create function that creates prompt:



func getRunner() promptui.Prompt {
return promptui.Prompt{Label: "Input your Email",
Validate: emailValidations,
}
}


Write simple assert test to verify that we create correct structure.



The only not tested line will be setEmail(getRunner()) but it is trivial and can be covered by other types of tests.






share|improve this answer













You should not try to test promptui as it is expected to be tested by its author.



What you can test:




  1. You send correct parameters when you create promptui.Prompt

  2. You use that promptui.Prompt in your code

  3. You properly handle promptui.Prompt results


As you can see, all these tests does not verify if promptui.Prompt works correctly inside.



Tests #2 and #3 could be combined. You need to run you code against mock and if you got correct result, you can believe that both #2 and #3 are correct.



Create mock:



type Runner interface {
Run() (int, string, error)
}

type promptMock struct {
// t is not required for this test, but it is would be helpful to assert input parameters if we have it in Run()
t *testing.T
}

func (p promptMock) Run() (int, string, error) {
// return expected result
return 1, "", nil
}


You will need separate mock for testing error flow.



Update your code to inject mock:



func setEmail(runner Runner) string {
email, err := runner.Run()
if err != nil {
color.red("failed getting email")
os.exit(3)
}
return email
}


Now it is testable.



Create function that creates prompt:



func getRunner() promptui.Prompt {
return promptui.Prompt{Label: "Input your Email",
Validate: emailValidations,
}
}


Write simple assert test to verify that we create correct structure.



The only not tested line will be setEmail(getRunner()) but it is trivial and can be covered by other types of tests.







share|improve this answer












share|improve this answer



share|improve this answer










answered Nov 14 '18 at 18:54









Dmitry HarnitskiDmitry Harnitski

3,79211833




3,79211833













  • Thank you for your help.

    – Steve
    Nov 14 '18 at 19:02



















  • Thank you for your help.

    – Steve
    Nov 14 '18 at 19:02

















Thank you for your help.

– Steve
Nov 14 '18 at 19:02





Thank you for your help.

– Steve
Nov 14 '18 at 19:02













1














For whatever reason, they don't export their stdin interface (https://github.com/manifoldco/promptui/blob/master/prompt.go#L49), so you can't mock it out, but you can directly mock os.Stdin and prefill it with whatever you need for testing. Though I agree with @Adrian, it has its own tests, so this shouldn't be necessary.



Extracted and refactored/simplified from source: Fill os.Stdin for function that reads from it



Refactored this way, it can be used for any function that reads from os.Stdin and expects a specific string.



Playground link: https://play.golang.org/p/rjgcGIaftBK



func TestSetEmail(t *testing.T) {
if err := TestExpectedStdinFunc("email@test.com", setEmail); err != nil {
t.Error(err)
return
}
fmt.Println("success")
}

func TestExpectedStdinFunc(expected string, f func() string) error {
content := byte(expected)
tmpfile, err := ioutil.TempFile("", "example")
if err != nil {
return err
}

defer os.Remove(tmpfile.Name()) // clean up

if _, err := tmpfile.Write(content); err != nil {
return err
}

if _, err := tmpfile.Seek(0, 0); err != nil {
return err
}

oldStdin := os.Stdin
defer func() { os.Stdin = oldStdin }() // Restore original Stdin

os.Stdin = tmpfile
actual := f()
if actual != expected {
return errors.New(fmt.Sprintf("test failed, exptected: %s actual: %s", expected, actual))
}

if err := tmpfile.Close(); err != nil {
return err
}
return nil
}





share|improve this answer


























  • Thanks for your help.

    – Steve
    Nov 14 '18 at 18:53
















1














For whatever reason, they don't export their stdin interface (https://github.com/manifoldco/promptui/blob/master/prompt.go#L49), so you can't mock it out, but you can directly mock os.Stdin and prefill it with whatever you need for testing. Though I agree with @Adrian, it has its own tests, so this shouldn't be necessary.



Extracted and refactored/simplified from source: Fill os.Stdin for function that reads from it



Refactored this way, it can be used for any function that reads from os.Stdin and expects a specific string.



Playground link: https://play.golang.org/p/rjgcGIaftBK



func TestSetEmail(t *testing.T) {
if err := TestExpectedStdinFunc("email@test.com", setEmail); err != nil {
t.Error(err)
return
}
fmt.Println("success")
}

func TestExpectedStdinFunc(expected string, f func() string) error {
content := byte(expected)
tmpfile, err := ioutil.TempFile("", "example")
if err != nil {
return err
}

defer os.Remove(tmpfile.Name()) // clean up

if _, err := tmpfile.Write(content); err != nil {
return err
}

if _, err := tmpfile.Seek(0, 0); err != nil {
return err
}

oldStdin := os.Stdin
defer func() { os.Stdin = oldStdin }() // Restore original Stdin

os.Stdin = tmpfile
actual := f()
if actual != expected {
return errors.New(fmt.Sprintf("test failed, exptected: %s actual: %s", expected, actual))
}

if err := tmpfile.Close(); err != nil {
return err
}
return nil
}





share|improve this answer


























  • Thanks for your help.

    – Steve
    Nov 14 '18 at 18:53














1












1








1







For whatever reason, they don't export their stdin interface (https://github.com/manifoldco/promptui/blob/master/prompt.go#L49), so you can't mock it out, but you can directly mock os.Stdin and prefill it with whatever you need for testing. Though I agree with @Adrian, it has its own tests, so this shouldn't be necessary.



Extracted and refactored/simplified from source: Fill os.Stdin for function that reads from it



Refactored this way, it can be used for any function that reads from os.Stdin and expects a specific string.



Playground link: https://play.golang.org/p/rjgcGIaftBK



func TestSetEmail(t *testing.T) {
if err := TestExpectedStdinFunc("email@test.com", setEmail); err != nil {
t.Error(err)
return
}
fmt.Println("success")
}

func TestExpectedStdinFunc(expected string, f func() string) error {
content := byte(expected)
tmpfile, err := ioutil.TempFile("", "example")
if err != nil {
return err
}

defer os.Remove(tmpfile.Name()) // clean up

if _, err := tmpfile.Write(content); err != nil {
return err
}

if _, err := tmpfile.Seek(0, 0); err != nil {
return err
}

oldStdin := os.Stdin
defer func() { os.Stdin = oldStdin }() // Restore original Stdin

os.Stdin = tmpfile
actual := f()
if actual != expected {
return errors.New(fmt.Sprintf("test failed, exptected: %s actual: %s", expected, actual))
}

if err := tmpfile.Close(); err != nil {
return err
}
return nil
}





share|improve this answer















For whatever reason, they don't export their stdin interface (https://github.com/manifoldco/promptui/blob/master/prompt.go#L49), so you can't mock it out, but you can directly mock os.Stdin and prefill it with whatever you need for testing. Though I agree with @Adrian, it has its own tests, so this shouldn't be necessary.



Extracted and refactored/simplified from source: Fill os.Stdin for function that reads from it



Refactored this way, it can be used for any function that reads from os.Stdin and expects a specific string.



Playground link: https://play.golang.org/p/rjgcGIaftBK



func TestSetEmail(t *testing.T) {
if err := TestExpectedStdinFunc("email@test.com", setEmail); err != nil {
t.Error(err)
return
}
fmt.Println("success")
}

func TestExpectedStdinFunc(expected string, f func() string) error {
content := byte(expected)
tmpfile, err := ioutil.TempFile("", "example")
if err != nil {
return err
}

defer os.Remove(tmpfile.Name()) // clean up

if _, err := tmpfile.Write(content); err != nil {
return err
}

if _, err := tmpfile.Seek(0, 0); err != nil {
return err
}

oldStdin := os.Stdin
defer func() { os.Stdin = oldStdin }() // Restore original Stdin

os.Stdin = tmpfile
actual := f()
if actual != expected {
return errors.New(fmt.Sprintf("test failed, exptected: %s actual: %s", expected, actual))
}

if err := tmpfile.Close(); err != nil {
return err
}
return nil
}






share|improve this answer














share|improve this answer



share|improve this answer








edited Nov 14 '18 at 21:15

























answered Nov 14 '18 at 18:44









RayfenWindspearRayfenWindspear

3,6631229




3,6631229













  • Thanks for your help.

    – Steve
    Nov 14 '18 at 18:53



















  • Thanks for your help.

    – Steve
    Nov 14 '18 at 18:53

















Thanks for your help.

– Steve
Nov 14 '18 at 18:53





Thanks for your help.

– Steve
Nov 14 '18 at 18:53


















draft saved

draft discarded




















































Thanks for contributing an answer to Stack Overflow!


  • Please be sure to answer the question. Provide details and share your research!

But avoid



  • Asking for help, clarification, or responding to other answers.

  • Making statements based on opinion; back them up with references or personal experience.


To learn more, see our tips on writing great answers.




draft saved


draft discarded














StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53306447%2fhow-do-i-unit-test-this-promptui-package-written-in-golang%23new-answer', 'question_page');
}
);

Post as a guest















Required, but never shown





















































Required, but never shown














Required, but never shown












Required, but never shown







Required, but never shown

































Required, but never shown














Required, but never shown












Required, but never shown







Required, but never shown







這個網誌中的熱門文章

Tangent Lines Diagram Along Smooth Curve

Yusuf al-Mu'taman ibn Hud

Zucchini