hypersolutions / hypermock

Apache License 2.0
8 stars 3 forks source link

Reset Occurence counts #9

Closed Micro666 closed 7 years ago

Micro666 commented 7 years ago

Hi,

I'm loving HyperMock for UWP, but it would be really useful if we could reset the Occurrence count on mocks.

This is useful where I have an MSTest ClassInitializer method, that sets up the mocks I need, and other setup code, rather than creating a mock in each and every TestMethod.

Currently if I setup the mocks once in the ClassInitializer method, in the TestMethods, when I try and verify occurences, the counters are not reset after each test method has run.

So for the first testmethod, I verify a property setter in the Mock for Occurs.Once(). that passes. In the next testmethod, I verify the same property setter in the Mock for Occurs.Never(). That fails, even though in that particular testmethod, the property setter on the Mock hasn't been called.

The failed test shows that the Occurrence count is 1 instead of the expected 0. This is because of the first testmethod that did call the property setter.

Moq has added an extension to allow the resetting of occurrence counts. Could HyperMock please as the same functionality? It would be so useful, and increase code re-use in test classes / methods.

Thanks

hypersolutions commented 7 years ago

Thanks for reporting the issue. I think I understand what you are asking but if you could attach an example with a failing test/scenario I will take a look for you and put in place a suitable solution or suggest an alternative approach. Given the feature exists in Moq then it would make sense for HyperMock to also provide something similar.

Micro666 commented 7 years ago

Hi,

Thanks for the quick response. Attached is an example test class, containing two test methods. As you can see from the example, I create and initialise the mocks in the ClassInitializer method. The mock services are assigned to static variables, as I wish to re-use the mocks.

In the full test class, there are a lot more test methods, and if I were to create and initialise the mocks in every method, there would be a lot of code duplication.

The test method TestAuthenticateUserValid2 passed on the "mockSettingsService.VerifySet" call, as the AuthenticationService under test, sets the CurrentUser property on the mockSettingsService.

The test method TestAuthenticateUserInvalid2 fails on the mockSettingsService.VerifySet, even though the CurrentUser property is not set. Debugging through the code also shows that the code path does not set the property.

However, because the mockSettingsService is created once, and used by both test methods, the occurrence count on the verification of the property setter is unexpectedly 1, rather than 0 in TestAuthenticateUserInvalid2.

I guess this occurs as one test method runs, and causes the property to be set on the mockSettingsService, whilst the second test method runs, and sees the verification occurrence count from the previous execution.

I hope that makes sense.

What I'm looking to do, is something the Moq team added, is a way to specify, for a specific Mock, to reset the occurrence counts, thereby allowing re-use of mocks.

If you need any more sample, please let me know.

Thx

hypersolutions commented 7 years ago

Hi, seems like the attachment hasn't come through. Maybe just paste the code in and I will sort it out.

Also I am wondering if a test base class like the one described here will help.

This avoids the need to setup each subject mocks for each test and you can simply request the mock via the MockFor helper method.

Micro666 commented 7 years ago

using System.Threading.Tasks; using GalaSoft.MvvmLight.Ioc; using HyperMock; using Microsoft.VisualStudio.TestTools.UnitTesting; using Titan.Infrastructure.DataServices; using Titan.Infrastructure.Interfaces; using Titan.Infrastructure.Models; using Titan.Infrastructure.Services;

namespace Titan.UnitTest.ServiceTests { [TestClass] public class AuthenticationServiceTestsExample { private static Mock mockDataService; private static Mock mockSettingsService; private static IAuthenticationService authenticationService;

    [ClassInitialize]
    public static void Initialise(TestContext context)
    {
        authenticationService = new AuthenticationService();

        // Create and setup mock data service
        mockDataService = Mock.Create<IDataService>();

        mockDataService
            .Setup(s => s.GetUserByUsernameAndPassword("TestUser", "TestPassword"))
            .Returns(Task.Run(() =>
                new DataServiceResponse<UserModel>
                {
                    ResponseType = ResponseTypes.Success,
                    Result = new UserModel(),
                }));

        // Setup mock for an invalid user
        mockDataService
            .Setup(s => s.GetUserByUsernameAndPassword(Param.IsAny<string>(), Param.IsAny<string>()))
            .Returns(Task.Run(() =>
                new DataServiceResponse<UserModel>
                {
                    ResponseType = ResponseTypes.HttpError,
                    Result = null,
                    Error = new ErrorModel(),
                }));

        // Create mock settings service
        mockSettingsService = Mock.Create<ISettingsService>();

        // Initialise the ServiceLocator with the mock services used by the IAuthenticationService
        ServiceLocator.Current.InitialiseServiceLocator(() =>
        {
            SimpleIoc.Default.Reset();
            SimpleIoc.Default.Register(() => mockDataService.Object);
            SimpleIoc.Default.Register(() => mockSettingsService.Object);
        });
    }

    [TestMethod]
    public async Task TestAuthenticateUserValid2()
    {
        var authenticatedUserResult = await authenticationService.AuthenticateUser("TestUser", "TestPassword");

        mockDataService.Verify(s => s.GetUserByUsernameAndPassword("TestUser", "TestPassword"), Occurred.Once());
        mockSettingsService.VerifySet(s => s.CurrentUser, Occurred.Once());

        Assert.IsTrue(authenticatedUserResult);
    }

    [TestMethod]
    public async Task TestAuthenticateUserInvalid2()
    {
        var authenticatedUserResult = await authenticationService.AuthenticateUser("Hacker", "LetMeIn");

        mockDataService.Verify(s => s.GetUserByUsernameAndPassword("Hacker", "LetMeIn"), Occurred.Once());
        mockSettingsService.VerifySet(s => s.CurrentUser, Occurred.Never());

        Assert.IsFalse(authenticatedUserResult);
    }
}

}

Micro666 commented 7 years ago

I've looked over the TestBase classes, and they look great, but only where the dependency injection pattern used via constructor parameters.

Unfortunately the project I'm working on, I've inherited, and throughout they are using the locator pattern to "pull" in services, into the classes that need them e.g. IAuthenticationService etc

hypersolutions commented 7 years ago

That's fine. Thanks for attaching the example. I shall take a look shortly.

hypersolutions commented 7 years ago

Please find a new NuGet package available version 2.0.7 containing a feature supporting the above request. You can find further details here but basically from the above code you can do the following:

using (Mock.CallGroup(mockDataService, mockSettingsService)
{
    // Do your calls here
}

On the exit of the dispose it will reset the counts automatically.

I prefer this approach as it will mitigate a failing test also failing the next one if for some reason the current test fails before the reset occurs.

If this satisfies your needs then please do close the issue.

Thanks

Micro666 commented 7 years ago

Hi,

That's perfect thanks. I agree that the approach you've provided, by using the disposable MockCallGroupContainer returned from Mock.CallGroup, is preferable, and ensures that counts are correctly reset in all cases.

Many thanks for such a speedy response to the issue! 👍

I'll certainly be spreading the word about HyperMock, as really it's the only viable mocking framework for UWP, and is as easy to use as Moq was with WPF :-)

Thanks again,

Glenn