NSubstitute入門(一)創造物件

單元測試系列

寫單元測試時,都需要隔離外部服務
把一些用到做法,記錄下來

寫單元測試的時候會用到隔離框架,隔離框架最常用的是NSubstitute,
是專門用Mock,stub...可以拿來做假物件,自訂回傳內容,做隔離測試

    [TestFixture]
    public class DemoTest
    {
        [Test]
        public void CalculatorTestDemo()
        {
            //Create 
            var calculator = Substitute.For<ICalculator>();
            //Set
            calculator.Add(1, 2).Returns(3);
            Assert.AreEqual(3, calculator.Add(1, 2));
        }
    }

    public interface ICalculator
    {
        int Add(int num1, int num2);
        string Mode { get; set; }
    }

Substitute.For<ICalculator>();是專門在建立假物件

calculator.Add(1, 2).Returns(3);設定回傳值為3

上例就會依據設定的方法回傳3

        [Test]
        public void Test_GetStarted_ReceivedSpecificCall()
        {
            ICalculator calculator = Substitute.For<ICalculator>();
            calculator.Add(1, 2);
            calculator.Received().Add(1, 2);
            calculator.DidNotReceive().Add(5, 7);
        }

calculator.Received().Add(1, 2); 確認接收是否,有調用到此方法

calculator.DidNotReceive().Add(5, 7);確認接收是否,沒有調用到此方法
 

        //example4
        [Test]
        public void Test_GetStarted_SetPropertyValue()
        {
            ICalculator calculator = Substitute.For<ICalculator>();
            calculator.Mode.Returns("DESC");
            Assert.AreEqual("DESC", calculator.Mode);
        }
        [Test]
        public void Test_GetStarted_MultipleValues()
        {
            ICalculator calculator = Substitute.For<ICalculator>();
            calculator.Mode.Returns("DESC", "DEC", "BEN");
            Assert.AreEqual("DESC", calculator.Mode);
            Assert.AreEqual("DEC", calculator.Mode);
            Assert.AreEqual("BEN", calculator.Mode);
        }

上方是用Return設定參數與多個參數值

 

        //example 5
        [Test]
        public void Test_GetStarted_Matchrguments()
        {
            ICalculator calculator = Substitute.For<ICalculator>();
            calculator.Add(10, -5);
            calculator.Received().Add(10, Arg.Any<int>());
            calculator.Received().Add(10, Arg.Is<int>(x=>x < 0));
        }

   任意(數字),型別=數字,Arg.Any<型別>())  EX:Arg.Any<int>()
   指定條件Arg.Is<型別>(),小於0的條件方式 EX:Arg.Is<int>(x=>x < 0)​

實例:

假設我要進行做會員登入成功和失敗,必須做隔離測試,如何做呢?

Model定義:

using System;

namespace Model
{
    public class Member
    {
        public string UserId { get; set; }
        public string  Password { get; set; }
        public string Name { get; set; }
        public string Email { get; set; }
    }
}

Define Repository Layer Interface and implement

using System;
using System.Collections.Generic;
using System.Text;
using Model;
namespace Repository.Interface
{
    public interface IMemberRepository
    {
        Member GetByMember(string userid, string password);
    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using Model;
using Repository.Interface;

namespace Repository
{
    public class MemberRepository : IMemberRepository
    {
        public List<Member> members = new List<Member>() {
            new Member{ UserId = "test001",
                        Password = "test002",
                        Name ="Eddie",
                        Email = "aaa@gmail.com"}
        };
        public Member GetByMember(string userid, string password)
               => members.Where(m => m.UserId == userid && m.Password == password).FirstOrDefault();

    }
}

Define Service Layer Interface and implement

using System;
using System.Collections.Generic;
using System.Text;

namespace Service.Interface
{
    public interface IMemberService
    {
         bool Login(string userId, string password);
    }
}
using Repository.Interface;
using Service.Interface;
using System;

namespace Service
{
    public class MemberService : IMemberService
    {
        public readonly IMemberRepository _memberRepository;
        public MemberService(IMemberRepository memberRepository)
        {
            _memberRepository = memberRepository;
        }
        public bool Login(string userid, string password)
        {
            var member = _memberRepository.GetByMember(userid, password);
            if (member == null)
                return false;
        
            return true;
        }
    }
}

Create Unit Test Login Success and Login Fail

using NUnit.Framework;
using NSubstitute;
using Repository.Interface;
using Model;
using Service.Interface;
using Service;

namespace Tests
{
    [TestFixture]
    public class MemberTests
    {
        private IMemberRepository _memberRepository;
        private IMemberService _memberService;
        private Member _setReturnMember;
        [SetUp]
        public void Setup()
        {
            _memberRepository = Substitute.For<IMemberRepository>();
            _memberService = new MemberService(_memberRepository);
            _setReturnMember = new Member()
            {
                UserId = "test001",
                Password = "test001",
                Name = "test",
                Email = "abc@gmail.com"
            };
        }

        [Test]
        public void Login_ReturnSuccess()
        {
            string userid = "test001";
            string password = "test001";
            _memberRepository.GetByMember(userid,password).Returns(_setReturnMember);
            bool result = _memberService.Login(userid, password);
            Assert.IsTrue(result);
        }

        [Test]
        public void Login_ReturnFail()
        {
            string userid = "test001";
            string password = "test002";
            _memberRepository.GetByMember(userid, password).Returns(m => null);
            bool result = _memberService.Login(userid, password);
            Assert.IsFalse(result);
        }
    }
}


以上是使用的情境

元哥的筆記