开发者

Overriding previously set function behavior of Typemock Isolator (WillThrow()/DoInstead())

开发者 https://www.devze.com 2023-03-15 18:46 出处:网络
Please check out this code: public UserRepository GetUserRepositoryMock() { // mock all UserRepository instances.

Please check out this code:

        public UserRepository GetUserRepositoryMock()
        {
            // mock all UserRepository instances.
            var userRepositoryMock = Isolate.Fake.Instance<UserRepository>();
            Isolate.Swap.AllInstances<UserRepository>().With(userRepositoryMock);

            // make all public UserRepository members throw a NotSupportedException.
            Isolate.WhenCalled(() => userRepositoryMock.Select(null)).WillThrow(new NotSupportedException());
            Isolate.WhenCalled(() => userRepositoryMock.SelectOne(null)).WillThrow(new NotSupportedException());
            Isolate.WhenCalled(() => userRepositoryMock.Save(null)).WillThrow(new NotSupportedException());
            Isolate.WhenCalled(() => userRepositoryMock.Remove(null)).WillThrow(new NotSupportedException());
            // ... et cetera until all public members of UserRepository will throw NotSupportedException.
        }

        [TestMethod]
        public void ActivateUser_UserNotFound_ThrowsException()
        {
            var userRepositor开发者_开发技巧yMock = GetUserRepositoryMock();

            // override one of the public UserRepository members to return a specific value.
            Isolate.WhenCalled(() => userRepositoryMock.SelectOne(null)).DoInstead(context =>
            {
                return null;
            });

            // call ActivateUser implementation.
            RegistrationServices.ActivateUser("foo@bar.com", "password", "activation-code");
        }

What this piece of code actually does is throw a NotSupportedException for all public members of UserRepository.

What I want this piece of code to do is make all public members of UserRepository throw a NotSupportedException except for the SelectOne() function, which returns null.

I want to make sure the ActivateUser() function of RegistrationServices does not call any function of the UserRepository other than the SelectOne() function I explicitly specified.

If it does, for example by changing the implementation of the ActivateUser() to make a call to Save() of UserRepository and not altering the corresponding *ActivateUser_UserNotFound_ThrowsException* test, I want the test to fail because the change might introduce unexpected behaviour. This way I can completely isolate my application from third parties and reduce coding errors to a minimum.

My questions regarding to this code and the principles behind it are:

  1. How can I achieve the desired behaviour?
  2. Are there any alternatives I can explore to achieve the desired behaviour?
  3. Are the basic principles of the desired behaviour valid? i.e. Should I want to isolate the entire application from third parties for testing purposes, raise exceptions when unsuspected function is called and only return valid data when explicitly specified so?


Note: I work at Typemock

There are several approaches you can take here to test what you want. For example, you can use the Isolate.Verify API to make sure no specific calls were made on your fake object.

This would allow you to not specify explicitly the return of other methods, as you can make sure that they did not occur:

    [Test, Isolated]
    public void ActivateUser_UserNotFound_ThrowsException()
    {
        var userRepositoryMock = Isolate.Fake.Instance<UserRepository>();
        Isolate.Swap.AllInstances<UserRepository>().With(userRepositoryMock);

        Isolate.WhenCalled(() => userRepositoryMock.SelectOne(null)).WillReturn(null);

        // call ActivateUser implementation.
        RegistrationServices.ActivateUser("foo@bar.com", "password", "activation-code");

        Isolate.Verify.WasNotCalled(() => userRepositoryMock.Save(null));
        Isolate.Verify.WasNotCalled(() => userRepositoryMock.Remove(null));
    }

Isolator's WhenCalled() chains method behaviors in the order defined, meaning that in your original test, the first time SelectOne would throw an exception, the second time and on it would return null.

Hope that helps, if you have other questions please feel free to contact us via support, or here!


I'm not sure why you want to do things this way. You would be better setting up your mock object inside the test and creating a new mock object for each test.

this might seem like more work at first, but using the approach you have above will make your tests hard to follow and the GetUserRepositoryMock method less and less useful as you add more tests which want slightly different behaviour.

if what you want to do is just verify that those methods were not called then you can do this with the Isolate.Verify.WasNotCalled(x=>x.Select(null)); for each of the methods. I think this is a better approach that your setting up all methods to throw exceptions.

You could get something along the lines you want by having your GetUserRepositoryMock method take a lot of booleans which specify which methods to mock with the throw NotImplementedException, all defaulted to true. Then you could use named parameters to just specify the one you don't want to set. Something along these lines:

    public UserRepository GetUserRepositoryMock(bool mockSelect=true, bool mockSelectOne=true, bool mockSave=true ... etc etc)
    {
        // mock all UserRepository instances.
        var userRepositoryMock = Isolate.Fake.Instance<UserRepository>();
        Isolate.Swap.AllInstances<UserRepository>().With(userRepositoryMock);

        // make all public UserRepository members throw a NotSupportedException.
        if(mockSelect) 
            Isolate.WhenCalled(() => userRepositoryMock.Select(null)).WillThrow(new NotSupportedException());
        if (mockSelectOne)
            Isolate.WhenCalled(() => userRepositoryMock.SelectOne(null)).WillThrow(new NotSupportedException());
        if(mockSave)
            Isolate.WhenCalled(() => userRepositoryMock.Save(null)).WillThrow(new NotSupportedException());

        // ... et cetera until all public members of UserRepository will throw NotSupportedException where the corresponding flag is true.
    }

then you could invoke it like this in your test where you don't want the SelectOne to be mocked:

    [TestMethod]
    public void ActivateUser_UserNotFound_ThrowsException()
    {
        var userRepositoryMock = GetUserRepositoryMock(mockSelectOne:false);

        // override one of the public UserRepository members to return a specific value.
        Isolate.WhenCalled(() => userRepositoryMock.SelectOne(null)).DoInstead(context =>
        {
            return null;
        });

        // call ActivateUser implementation.
        RegistrationServices.ActivateUser("foo@bar.com", "password", "activation-code");
    }

So you only have to specify the things which you want to be different from the default, using the named parameters.

I'm not saying I like this solution particularly, and there might well be a better solution using some of TypeMocks own methods, but this should give you what you want. You could tailor this method to actually do the Isolate.Verify.WasNotCalled() checks for each thing instead, based on the flags.

0

精彩评论

暂无评论...
验证码 换一张
取 消