Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What's the correct way to use Stubs and Mocks?

Here's my example:

[TestMethod]
public void NewAction_should_return_IndexAction()
{
    NewViewModel viewModel = new NewViewModel()
    {
        Name = "José Inácio Santos Silva",
        Email = "[email protected]",
        Username = "joseinacio"
    };

    //IsUserRegistered is used to validate Username, Username is unique.
    _mockAuthenticationService.Setup(x => x.IsUserRegistered(viewModel.Username )).Returns(false);

    //IsUserRegistered is used to validate Email, Email is unique.
    _mockUsuarioRepository.Setup(x => x.GetUserByEmail(viewModel.Email));
    _mockDbContext.Setup(x => x.SaveChanges());
    _mockUsuarioRepository.Setup(x => x.Add(It.IsAny<User>()));

    _userController = new UserController(_mockUsuarioRepository.Object, _mockDbContext.Object, _mockAuthenticationService.Object);

    ActionResult result = _userController.New(viewModel);

    result.AssertActionRedirect().ToAction("Index");

    _mockAuthenticationService.VerifyAll();
    _mockUsuarioRepository.VerifyAll();
    _mockDbContext.VerifyAll();
}

I have read some tutorials and they say that we should use only one mock per test.

But look at my test, it use 3 mocks, to check if my Action is working the right way I need to check these 3 mocks, do not agree?

How do I make this test in the correct way?

like image 429
Acaz Souza Avatar asked Sep 06 '11 16:09

Acaz Souza


2 Answers

Each unit test should test only one thing.

In your unit test you are testing three mock objects. If the mockAuthenticationService fails, this will be reported and the unit test will stop there. Any errors with the other Mock objects are not reported and are effectively hidden.

In this situation you should create three unit tests, and in each one verify only one of the Mock objects. The rest are just used as stubs. (A stub is exactly the same as a Mock object, except you dont call VerifyAll on it at the end)

To avoid duplication and wasted effort, you should refactor that unit test so that most of the code is in a separate method. Each of the three unit tests calls this method and then verifies a single Mock.

You also have a test to ensure the correct redirect is called. This should also be in a separate test.

Quite simply:

[TestMethod]
public void NewAction_should_checkUserRegistered()
{
    SetupTest();
    _mockAuthenticationService.VerifyAll();
}

[TestMethod]
public void NewAction_should_GetUserByEmail()
{
    SetupTest();
    _mockUsuarioRepository.VerifyAll();
}

[TestMethod]
public void NewAction_should_SaveDBContext()
{
    SetupTest();
    _mockDbContext.VerifyAll();
}

[TestMethod]
public void NewAction_should_return_Redirects_Action()
{
    var novoActionResult = SetupTest();
    novoActionResult.AssertActionRedirect().ToAction("Index");
}
like image 69
Mongus Pong Avatar answered Oct 25 '22 20:10

Mongus Pong


Short answer: "only one mock per test." is ambiguous. Use as many fakes as you need to isolate the code under test to a "unit" that is testing one condition. It should be phrased: Only test one thing per test. If you are checking the state of more than one mock object you are probably testing more than one thing.


Long answer:

There is a lot to answer here to get the unit test written according to the best practices I have come across.

Common terminology from (The Art of Unit Testing), which I hope will come to be common:

Fake - an object that isolates the code under test from the rest of the application.
Stub - a simple fake object.
Mock - a fake object that stores what is passed to it, that you can inspect to verify the test.
Stubs and Mocks are both types of fake.

"only one mock per test." is wrong. You use as many fakes as you need to fully isolate the code under test from the rest of the application. If a method takes no parameters, there's nothing to fake. If a method takes a simple data type e.g. int, string, that doesn't have any complex behaviour, you don't need to fake it. If you have 2 repositories, context, a service object passed in, fake all of them, so no other production methods are being called.

You should have one condition per test as @Mongus Pong has said.

Test naming convention: MethodUnderTest_Condition_ExpectedBehaviour in this case you cannot do that as you have got more than one condition tested.

Test pattern: Arrange, Act, Assert. From your test, it seems as that is what you have done, but you have are arranging using private members. You should replace these with variables in each test, since the running order of tests is not always enforced, the state of these variables cannot be guaranteed, making your tests unreliable.

Buy a copy of "The Art of Unit Testing" http://artofunittesting.com/ it will answer a lot of more of your questions and is a great investment; one of the books that I'd grab if the office caught fire.

like image 44
StuperUser Avatar answered Oct 25 '22 20:10

StuperUser