如何在测试方法中模拟按键?

时间:2019-04-19 13:34:29

标签: c# .net unit-testing nunit moq

我编写了一个生活控制台应用程序游戏,现在我正在为其编写单元测试。游戏板呈现为一个循环,可以通过Esc按下来断开。但是我不知道如何在主应用程序类的测试方法中模拟该按键,因此我的测试当前无限期地循环。

Application.cs

public class Application
{
    private readonly IGame _game;
    private readonly IBoard _board;
    private readonly IBoardGenerator _boardGenerator;
    private readonly IConsole _console;

    public Application(IGame game, IBoard board, IBoardGenerator boardGenerator, IConsole console)
    {
        _game = game;
        _board = board;
        _boardGenerator = boardGenerator;
        _console = console;
    }
    public void Run()
    {
        void RenderBoard()
        {
            _console.Clear();
            _board.Evolve();
            _board.Print();
            Thread.Sleep(150);
        } 

        LoopUntilButtonPressed(() =>
        { 
            _console.Clear();
            _game.NewGame();
            _game.SetBoard(_board, _boardGenerator);
            LoopUntilButtonPressed(RenderBoard, ConsoleKey.Escape);
        }, ConsoleKey.Escape);
    }

    private void LoopUntilButtonPressed(Action action, ConsoleKey consoleKey)
    {
        do
        {
            while (!_console.KeyAvailable)
            {
                action.Invoke();
            }
        } while (_console.ReadKey(true) != consoleKey);
    }

ApplicationTests.cs

[TestFixture]
public class ApplicationTests
{
    private Mock<IGame> _fakeGame;
    private Mock<IBoard> _fakeBoard;
    private Mock<IBoardGenerator> _fakeBoardGenerator;
    private Mock<IConsole> _fakeConsole;
    private Application _application;

    [SetUp]
    public void SetUp()
    {
        _fakeGame = new Mock<IGame>();
        _fakeBoard = new Mock<IBoard>();
        _fakeBoardGenerator = new Mock<IBoardGenerator>();
        _fakeConsole = new Mock<IConsole>();
        _application = new Application(_fakeGame.Object, _fakeBoard.Object, _fakeBoardGenerator.Object, _fakeConsole.Object);
    }

    [Test]
    public void Run_MethodCalled_GameCorrectlySet()
    {
        _application.Run();
        _fakeConsole.Setup(c => c.ReadKey(It.IsAny<bool>())).Returns(ConsoleKey.Escape);
        _fakeConsole.Setup(c => c.KeyAvailable).Returns(true);

        _fakeGame.Verify(g => g.NewGame(), Times.Once);            
    }
}

1 个答案:

答案 0 :(得分:1)

在控制台抽象上模拟ReadKeyKeyAvailable成员。

确保Setup在被测方法之前发生。在这种情况下为Run。这样,模拟在被调用时将表现出预期的效果。

我还建议您为KeyAvailable设置一个序列,以便在while中使用模拟成员时可以调用中断条件。

[Test]
public void Run_MethodCalled_GameCorrectlySet() {
    //Arrange        
    _fakeConsole.Setup(_ => _.ReadKey(It.IsAny<bool>())).Returns(ConsoleKey.Escape);
    _fakeConsole.SetupSequence(_ => _.KeyAvailable)
        .Returns(false) // will be returned on 1st invocation
        .Returns(true); // will be returned on 2nd invocation to break while

    //Act
    _application.Run();

    //Assert
    _fakeGame.Verify(_ => _.NewGame(), Times.Once);            
}