Search code examples
c#eventsmoqprism

C# Moq verify publish event failed given custom event arguments


So I have the following ViewModel:

using Prism.Commands;
using Prism.Events;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
using TrackIt.Model;
using TrackIt.UI.Event;

namespace TrackIt.UI.ViewModel
{
    public class NavigationItemViewModel : ViewModelBase
    {
        private string _displayMember;
        private IEventAggregator _eventAggregator;
        private string _detailViewModelName;

        public NavigationItemViewModel(int id,
            IEventAggregator eventAggregator,
            string detailViewModelName)
        {
            Id = id;
            _detailViewModelName = detailViewModelName;
            OpenDetailViewCommand = new DelegateCommand(OnOpenDetailViewExecute);
            _eventAggregator = eventAggregator;
        }
        public int Id { get; }

        public ICommand OpenDetailViewCommand { get; set; }

        private void OnOpenDetailViewExecute()
        {
            _eventAggregator.GetEvent<TestEvent>()
                        .Publish(Id);
            _eventAggregator.GetEvent<OpenDetailViewEvent>()
                        .Publish(
                new OpenDetailViewEventArgs
                {
                    Id = Id,
                    ViewModelName = _detailViewModelName
                }
                );
        }
    }
}

Thus I have an ICommand above, that upon executing, will trigger the TestEvent and OpenDetailViewEvent events, with the TestEvent having an integer argument, and the OpenDetailViewEvent having a custom class argument OpenDetailViewEventArgs class.

I'm testing that upon executing the command, the Publish method of each event is called. I use Moq to mock the GetEvent calls. This is my code:

using Moq;
using Prism.Events;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using TrackIt.UI.Event;
using TrackIt.UI.ViewModel;
using Xunit;

namespace TrackIt.UITests.ViewModel
{
    public class NavigationItemViewModelTests
    {

        [Fact]
        public void ShouldPublishOpenFriendDetailViewEvent()
        {
            const string displayMember = "test string";
            const int id = 4;
            const string detailViewMemberName = nameof(FriendDetailViewModel); // Just take a random detail view

            var _mockedOpenDetailViewEvent = new Mock<OpenDetailViewEvent>();
            var _mockedTestEvent = new Mock<TestEvent>();
            var eventAggregatorMock = new Mock<IEventAggregator>();

            eventAggregatorMock
                .Setup(dp => dp.GetEvent<OpenDetailViewEvent>())
                .Returns(_mockedOpenDetailViewEvent.Object);
            eventAggregatorMock
                .Setup(dp => dp.GetEvent<TestEvent>())
                .Returns(_mockedTestEvent.Object);

            var _itemViewModel = new NavigationItemViewModel(id,
                 displayMember, eventAggregatorMock.Object, detailViewMemberName);

            _itemViewModel.OpenDetailViewCommand.Execute(null);

            _mockedTestEvent.Verify(e => e.Publish(id),
                                                Times.Once);
            _mockedOpenDetailViewEvent.Verify(e => e.Publish(new OpenDetailViewEventArgs { Id = id, ViewModelName = detailViewMemberName }), 
                                                Times.Once);
        }
    }
}

The thing is, the test passes the Verify for TestEvent event, but it fails for OpenDetailViewEvent with error Expected invocation on the mock once, but was 0 times, even though I debugged and saw the event actually published. Maybe it's because I'm supplying a custom class as its argument, while TestEvent uses a native C# class (i.e. int)? How to fix this?


Solution

  • Use an argument matcher instead since the instances can't be the same object reference in this scenario.

    // ....
    _mockedOpenDetailViewEvent
        .Verify(
            _ => _.Publish(It.Is<OpenDetailViewEventArgs>(arg => arg.Id == id && arg.ViewModelName == detailViewMemberName)),
            Times.Once
        );
    

    It.Is above is used to create a predicate to match the argument that was used to invoke the member.