271 ISP in Tests

PersonsServiceTest.cs

using AutoFixture;
using Entities;
using FluentAssertions;
using Moq;
using RepositoryContracts;
using ServiceContracts.DTO;
using ServiceContracts.Enums;
using System.Linq.Expressions;
using Xunit.Abstractions;
using Serilog;
using Microsoft.Extensions.Logging;
using ServiceContracts;
using Services;

namespace CRUDxUnitTests
{
    public class PersonsServiceTest
    {
        //private fields
        private readonly Mock<IPersonsRepository> _personsRepositoryMock;
        private readonly IPersonsRepository _personsRepository;

        //private fields
        private readonly IPersonsAdderService _personsAdderService;
        private readonly IPersonsDeleterService _personsDeleterService;
        private readonly IPersonsGetterService _personsGetterService;
        private readonly IPersonsSorterService _personsSorterService;
        private readonly IPersonsUpdaterService _personsUpdaterService;


        private readonly ITestOutputHelper _testOutputHelper;
        private readonly IFixture _fixture;

        //constructor
        public PersonsServiceTest(ITestOutputHelper testOutputHelper)
        {
            _fixture = new Fixture();
            _testOutputHelper = testOutputHelper;

            _personsRepositoryMock = new Mock<IPersonsRepository>();
            _personsRepository = _personsRepositoryMock.Object;

            var diagnosticContextMock = new Mock<IDiagnosticContext>();

            var loggerAdderMock = new Mock<ILogger<PersonsAdderService>>();
            var loggerDeleterMock = new Mock<ILogger<PersonsDeleterService>>();
            var loggerGetterMock = new Mock<ILogger<PersonsGetterService>>();
            var loggerSorterMock = new Mock<ILogger<PersonsSorterService>>();
            var loggerUpdaterMock = new Mock<ILogger<PersonsUpdaterService>>();

            _personsAdderService = new PersonsAdderService(_personsRepository, loggerAdderMock.Object, diagnosticContextMock.Object);
            _personsDeleterService = new PersonsDeleterService(_personsRepository, loggerDeleterMock.Object, diagnosticContextMock.Object);
            _personsGetterService = new PersonsGetterService(_personsRepository, loggerGetterMock.Object, diagnosticContextMock.Object);
            _personsSorterService = new PersonsSorterService(_personsRepository, loggerSorterMock.Object, diagnosticContextMock.Object);
            _personsUpdaterService = new PersonsUpdaterService(_personsRepository, loggerUpdaterMock.Object, diagnosticContextMock.Object);
        }

        #region AddPerson
        //When we supply null value as PersonAddRequest, it should throw ArgumentNullException
        [Fact]
        public async Task AddPerson_NullPerson_ToBeArgumentNullExceltion()
        {
            //Arrange
            PersonAddRequest? personAddRequest = null;

            //Act
            Func<Task> action = async () =>
            {
                await _personsAdderService.AddPerson(personAddRequest);
            };

            //Assert
            await action.Should().ThrowAsync<ArgumentNullException>();
            //await Assert.ThrowsAsync<ArgumentNullException>();
        }

        //When we supply null value as PersonName, it should throw ArgumentNullException
        [Fact]
        public async Task AddPerson_PersonNameIsNull_ToBeArgumentException()
        {
            //Arrange
            PersonAddRequest? personAddRequest = _fixture.Build<PersonAddRequest>()
                .With(p => p.PersonName, null as string).Create();

            Person person = personAddRequest.ToPerson();
            //When PersonsRepository.AddPerson is called, it has to return the same "person" object
            _personsRepositoryMock.Setup(p => p.AddPersonAsync(It.IsAny<Person>()))
                .ReturnsAsync(person);
            //Act
            Func<Task> action = async () =>
            {
                await _personsAdderService.AddPerson(personAddRequest);
            };

            //Assert
            await action.Should().ThrowAsync<ArgumentException>();
        }

        //When we supply proper person details, it should insert the person into the persons list
        //and it should return an object of PersonResponse, which includes with the newly generated person id
        [Fact]
        public async Task AddPerson_FullPersonDetails_ToBeSuccessful()
        {
            //Arrange
            //PersonAddRequest? personAddRequest = _fixture.Create<PersonAddRequest>();
            PersonAddRequest personAddRequest = _fixture.Build<PersonAddRequest>()
                .With(p => p.Email, "person@example.com").Create();

            Person person = personAddRequest.ToPerson();
            PersonResponse person_response_expected = person.ToPersonResponse();
            //If we supply any argument value to the AddPersonAsync method,
            //it should return the same return value
            _personsRepositoryMock.Setup(p => p.AddPersonAsync(It.IsAny<Person>()))
                .ReturnsAsync(person);
            //Act
            PersonResponse personResponse = await _personsAdderService.AddPerson(personAddRequest);
            person_response_expected.PersonId = personResponse.PersonId;
            //Assert
            personResponse.PersonId.Should().NotBe(Guid.Empty);
            personResponse.Should().Be(person_response_expected);
        }
        #endregion

        #region GetPersonByPersonId
        //If we supply null as PersonId, it should return null as PersonResponse
        [Fact]
        public async Task GetPersonByPersonId_NullPersonId_ToBeNull()
        {
            //Arrange
            Guid? personId = null;

            //Act
            PersonResponse? personResponse = await _personsGetterService.GetPersonByPersonId(personId);

            //Assert
            //Assert.Null(personResponse);
            personResponse.Should().BeNull();
        }

        //If we supply a valid person id, it should return thr valid person details as PersonResponse object
        [Fact]
        public async Task GetPersonByPersonId_WithPersonId_ToBeSuccessful()
        {
            //Arrange
            Person person = _fixture.Build<Person>()
                .With(p => p.Email, "email@example.com")
                .With(p => p.Country, null as Country)
                .Create();
            PersonResponse person_response_expected = person.ToPersonResponse();

            _personsRepositoryMock.Setup(p => p.GetPersonByPersonIdAsync(It.IsAny<Guid>()))
                .ReturnsAsync(person);
            //Act
            PersonResponse? personResponseGet = await _personsGetterService.GetPersonByPersonId(person.PersonId);
            //Assert
            //Assert.Equal(personResponseAdd, personResponseGet);
            personResponseGet.Should().BeEquivalentTo(person_response_expected);
        }

        #endregion

        #region GetAllPersons
        //The GetAllPersons() should return an empty list by default
        [Fact]
        public async Task GetAllPersons_ToBeEmptyList()
        {
            //Arrange
            List<Person> persons = new List<Person>();
            _personsRepositoryMock.Setup(p => p.GetAllPersonsAsync())
                .ReturnsAsync(persons);
            //Act
            List<PersonResponse> personResponses = await _personsGetterService.GetAllPersons();

            //Assert
            //Assert.Empty(personResponses);
            personResponses.Should().BeEmpty();
        }

        //First, we will add few persons, and then when we call GetAllPersons(), it should return the sample persons that were added
        [Fact]
        public async Task GetPersons_WithFewPersons_ToBeSuccessful()
        {
            //Arrange
            List<Person> persons = new List<Person>()
            {
                _fixture.Build<Person>()
                .With(p => p.PersonName, "Smith")
                .With(p => p.Email, "smith@example.com")
                .With(p => p.Country, null as Country)
                .Create(),
            _fixture.Build<Person>()
                .With(p => p.PersonName, "Marry")
                .With(p => p.Email, "marry@example.com")
                .With(p => p.Country, null as Country)
                .Create(),
            _fixture.Build<Person>()
                .With(p => p.PersonName, "Rahman")
                .With(p => p.Email, "rahman@example.com")
                .With(p => p.Country, null as Country)
                .Create()
            };


            List<PersonResponse> person_response_list_expected = persons.Select(p => p.ToPersonResponse()).ToList();

            //print personResponsesAdd
            _testOutputHelper.WriteLine("Expected:");
            foreach (PersonResponse item in person_response_list_expected)
            {
                _testOutputHelper.WriteLine(item.ToString());
            }

            _personsRepositoryMock.Setup(p => p.GetAllPersonsAsync())
                .ReturnsAsync(persons);

            //Act
            List<PersonResponse> personResponsesGet = await _personsGetterService.GetAllPersons();

            //print personResponsesGet
            _testOutputHelper.WriteLine("Actual:");
            foreach (PersonResponse item in personResponsesGet)
            {
                _testOutputHelper.WriteLine(item.ToString());
            }

            //Assert
            personResponsesGet.Should().BeEquivalentTo(person_response_list_expected);
        }
        #endregion

        #region GetFilteredPersons
        //If the search text is empty and search by is "PersonName", it should return all persons
        [Fact]
        public async Task GetFilteredPersons_EmptySearchText_ToBeSuccessful()
        {
            //Arrange
            List<Person> persons = new List<Person>()
            {
                _fixture.Build<Person>()
                .With(p => p.PersonName, "Smith")
                .With(p => p.Email, "smith@example.com")
                .With(p => p.Country, null as Country)
                .Create(),
            _fixture.Build<Person>()
                .With(p => p.PersonName, "Marry")
                .With(p => p.Email, "marry@example.com")
                .With(p => p.Country, null as Country)
                .Create(),
            _fixture.Build<Person>()
                .With(p => p.PersonName, "Rahman")
                .With(p => p.Email, "rahman@example.com")
                .With(p => p.Country, null as Country)
                .Create()
            };

            List<PersonResponse> person_response_list_expected = persons.Select(p => p.ToPersonResponse()).ToList();



            //print personResponsesAdd
            _testOutputHelper.WriteLine("Expected:");
            foreach (PersonResponse item in person_response_list_expected)
            {
                _testOutputHelper.WriteLine(item.ToString());
            }

            _personsRepositoryMock.Setup(p => p.GetFilteredPersonsAsync(It.IsAny<Expression<Func<Person, bool>>>()))
                .ReturnsAsync(persons);
            //Act
            List<PersonResponse> personResponsesSearch =
                await _personsGetterService.GetFilteredPersons(nameof(Person.PersonName), "");

            //print personResponsesGet
            _testOutputHelper.WriteLine("Actual:");
            foreach (PersonResponse item in personResponsesSearch)
            {
                _testOutputHelper.WriteLine(item.ToString());
            }

            //Assert
            personResponsesSearch.Should().BeEquivalentTo(person_response_list_expected);
        }

        //Search based on person name with some search string.
        //It should return the matching persons
        [Fact]
        public async Task GetFilteredPersons_SearchByPersonName_ToBeSuccessful()
        {
            //Arrange
            List<Person> persons = new List<Person>()
            {
                _fixture.Build<Person>()
                .With(p => p.PersonName, "Smith")
                .With(p => p.Email, "smith@example.com")
                .With(p => p.Country, null as Country)
                .Create(),
            _fixture.Build<Person>()
                .With(p => p.PersonName, "Marry")
                .With(p => p.Email, "marry@example.com")
                .With(p => p.Country, null as Country)
                .Create(),
            _fixture.Build<Person>()
                .With(p => p.PersonName, "Rahman")
                .With(p => p.Email, "rahman@example.com")
                .With(p => p.Country, null as Country)
                .Create()
            };

            List<PersonResponse> person_response_list_expected = persons.Select(p => p.ToPersonResponse()).ToList();



            //print personResponsesAdd
            _testOutputHelper.WriteLine("Expected:");
            foreach (PersonResponse item in person_response_list_expected)
            {
                _testOutputHelper.WriteLine(item.ToString());
            }

            _personsRepositoryMock.Setup(p => p.GetFilteredPersonsAsync(It.IsAny<Expression<Func<Person, bool>>>()))
                .ReturnsAsync(persons);
            //Act
            List<PersonResponse> personResponsesSearch =
                await _personsGetterService.GetFilteredPersons(nameof(Person.PersonName), "ma");

            //print personResponsesGet
            _testOutputHelper.WriteLine("Actual:");
            foreach (PersonResponse item in personResponsesSearch)
            {
                _testOutputHelper.WriteLine(item.ToString());
            }

            //Assert
            personResponsesSearch.Should().BeEquivalentTo(person_response_list_expected);
        }
        #endregion

        #region GetSortedPersons
        //When we sort based on PersonName in DESC, it should return persons list in descending on PersonName
        [Fact]
        public async Task GetSortedPersons_ToBeSuccessful()
        {
            //Arrange
            List<Person> persons = new List<Person>()
            {
                _fixture.Build<Person>()
                .With(p => p.PersonName, "Smith")
                .With(p => p.Email, "smith@example.com")
                .With(p => p.Country, null as Country)
                .Create(),
                _fixture.Build<Person>()
                .With(p => p.PersonName, "Marry")
                .With(p => p.Email, "marry@example.com")
                .With(p => p.Country, null as Country)
                .Create(),
                _fixture.Build<Person>()
                .With(p => p.PersonName, "Rahman")
                .With(p => p.Email, "rahman@example.com")
                .With(p => p.Country, null as Country)
                .Create()
            };

            List<PersonResponse> person_response_list_expected = persons.Select(p => p.ToPersonResponse())
                .OrderByDescending(p => p.PersonName).ToList();

            _personsRepositoryMock.Setup(p => p.GetAllPersonsAsync())
                .ReturnsAsync(persons);



            //print personResponsesAdd
            _testOutputHelper.WriteLine("Expected:");
            foreach (PersonResponse item in person_response_list_expected)
            {
                _testOutputHelper.WriteLine(item.ToString());
            }
            List<PersonResponse> allPersons = await _personsGetterService.GetAllPersons();
            //Act
            List<PersonResponse> persons_list_from_sort = await _personsSorterService
                .GetSortedPersons(allPersons, nameof(Person.PersonName), SortOrderOptions.DESC);
            //print personResponsesGet
            _testOutputHelper.WriteLine("Actual:");
            foreach (PersonResponse item in persons_list_from_sort)
            {
                _testOutputHelper.WriteLine(item.ToString());
            }

            //Assert
            for (int i = 0; i < person_response_list_expected.Count; i++)
            {
                Assert.Equal(person_response_list_expected[i], persons_list_from_sort[i]);
            }
            persons_list_from_sort.Should().BeInDescendingOrder(p => p.PersonName);
        }
        #endregion

        #region UpdatePerson
        //When we supply null as PersonUpdateRequest, it should throw ArgumentNullException
        [Fact]
        public async Task UpdatePerson_NullPerson_ToBeArgumentNullException()
        {
            //Arrange
            PersonUpdateRequest? personUpdateRequest = null;

            //Act
            Func<Task> action = async () =>
            {
                await _personsUpdaterService.UpdatePerson(personUpdateRequest);
            };

            //Assert
            await action.Should().ThrowAsync<ArgumentNullException>();
        }

        //When we supply invalid person id, it should throw ArgumentNullException
        [Fact]
        public async Task UpdatePerson_InvalidPersonId_ToBeArgumentException()
        {
            //Arrange
            PersonUpdateRequest? personUpdateRequest = _fixture.Build<PersonUpdateRequest>()
                .Create();

            //Act
            Func<Task> action = async () =>
            {
                await _personsUpdaterService.UpdatePerson(personUpdateRequest);
            };

            //Assert
            await action.Should().ThrowAsync<ArgumentException>();
        }

        //When PersonName is null, it should throw ArgumentException
        [Fact]
        public async Task UpdatePerson_PersonNameIsNull_ToBeArgumentException()
        {
            //Arrange
            Person person = _fixture.Build<Person>()
                .With(p => p.PersonName, null as string)
                .With(p => p.Email, "John@example.com")
                .With(p => p.Country, null as Country)
                .With(p => p.Gender, GenderOptions.Male.ToString())
                .Create();

            PersonResponse person_response_from_add = person.ToPersonResponse();

            PersonUpdateRequest personUpdateRequest = person_response_from_add.ToPersonUpdateRequest();
            _personsRepositoryMock.Setup(p => p.UpdatePersonAsync(It.IsAny<Person>()))
                .ReturnsAsync(person);
            //Act
            Func<Task> action = async () =>
            {
                await _personsUpdaterService.UpdatePerson(personUpdateRequest);
            };

            //Assert
            await action.Should().ThrowAsync<ArgumentException>();
        }

        //Frist, add a new person and try to update the person name and email
        [Fact]
        public async Task UpdatePerson_PersonFullDetailsUpdation_ToBeSuccessful()
        {
            //Arrange
            Person person = _fixture.Build<Person>()
                .With(p => p.PersonName, "John")
                .With(p => p.Email, "John@example.com")
                .With(p => p.Country, null as Country)
                .With(p => p.Gender, GenderOptions.Male.ToString())
                .Create();

            PersonResponse person_response_from_expected = person.ToPersonResponse();

            PersonUpdateRequest person_update_request = person_response_from_expected.ToPersonUpdateRequest();
            _personsRepositoryMock.Setup(p => p.UpdatePersonAsync(It.IsAny<Person>()))
                .ReturnsAsync(person);
            _personsRepositoryMock.Setup(p => p.GetPersonByPersonIdAsync(It.IsAny<Guid>()))
                .ReturnsAsync(person);
            //Act
            PersonResponse personResponseUpdate = await _personsUpdaterService.UpdatePerson(person_update_request);
            //Assert
            personResponseUpdate.Should().BeEquivalentTo(person_response_from_expected);
        }
        #endregion

        #region DeletePerson
        //If you supply an valid PersonId, it should return true
        [Fact]
        public async Task DeletePerson_ValidPersonId_ToBeTrue()
        {
            //Arrange
            Person person = _fixture.Build<Person>()
                .With(p => p.PersonName, "John")
                .With(p => p.Email, "John@example.com")
                .With(p => p.Country, null as Country)
                .With(p => p.Gender, GenderOptions.Male.ToString())
                .Create();

            PersonResponse personResponse = person.ToPersonResponse();

            _personsRepositoryMock.Setup(p => p.DeletePersonByPersonIdAsync(It.IsAny<Guid>()))
                .ReturnsAsync(true);
            _personsRepositoryMock.Setup(p => p.GetPersonByPersonIdAsync(It.IsAny<Guid>()))
                .ReturnsAsync(person);
            //Act
            bool isDeleted = await _personsDeleterService.DeletePerson(personResponse.PersonId);
            //Assert
            isDeleted.Should().BeTrue();
        }

        //If you supply an Invalid PersonId, it should return false
        [Fact]
        public async Task DeletePerson_InvalidPersonId_ToBeFalse()
        {
            
            //Act
            bool isDeleted = await _personsDeleterService.DeletePerson(Guid.NewGuid());
            //Assert
            isDeleted.Should().BeFalse();
        }
        #endregion
    }
}

PersonsControllerTest.cs

using AutoFixture;
using Moq;
using ServiceContracts;
using FluentAssertions;
using CRUDExample.Controllers;
using ServiceContracts.DTO;
using ServiceContracts.Enums;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;

namespace CRUDxUnitTests
{
    public class PersonsControllerTest
    {
        private readonly IFixture _fixture;

        private readonly Mock<ICountriesService> _countriesServiceMock;

        private readonly Mock<IPersonsAdderService> _personsAdderServiceMock;
        private readonly Mock<IPersonsDeleterService> 
_personsDeleterServiceMock;
        private readonly Mock<IPersonsGetterService> _personsGetterServiceMock;
        private readonly Mock<IPersonsSorterService> _personsSorterServiceMock;
        private readonly Mock<IPersonsUpdaterService> 
_personsUpdaterServiceMock;

        private readonly Mock<ILogger<PersonsController>> _loggerMock;

        private readonly ICountriesService _countriesService;

        //private fields
        private readonly IPersonsAdderService _personsAdderService;
        private readonly IPersonsDeleterService _personsDeleterService;
        private readonly IPersonsGetterService _personsGetterService;
        private readonly IPersonsSorterService _personsSorterService;
        private readonly IPersonsUpdaterService _personsUpdaterService;

        private readonly ILogger<PersonsController> _logger;

        public PersonsControllerTest()
        {
            _fixture = new Fixture();

            _countriesServiceMock = new Mock<ICountriesService>();

            _personsAdderServiceMock = new Mock<IPersonsAdderService>();
            _personsDeleterServiceMock = new Mock<IPersonsDeleterService>();
            _personsGetterServiceMock = new Mock<IPersonsGetterService>();
            _personsSorterServiceMock = new Mock<IPersonsSorterService>();
            _personsUpdaterServiceMock = new Mock<IPersonsUpdaterService>();

            _loggerMock = new Mock<ILogger<PersonsController>>();

            _countriesService = _countriesServiceMock.Object;

            _personsAdderService = _personsAdderServiceMock.Object;
            _personsDeleterService = _personsDeleterServiceMock.Object;
            _personsGetterService = _personsGetterServiceMock.Object;
            _personsSorterService = _personsSorterServiceMock.Object;
            _personsUpdaterService = _personsUpdaterServiceMock.Object;

            _logger = _loggerMock.Object;
        }

        //Test Cases
        #region Index
        [Fact]
        public async Task Index_ShouldReturnIndexViewWithPersonsList()
        {
            //Arrange
            List<PersonResponse> persons_response_list = 
_fixture.Create<List<PersonResponse>>();

            _personsGetterServiceMock.Setup(p => 
p.GetFilteredPersons(It.IsAny<string>(), It.IsAny<string>()))
                .ReturnsAsync(persons_response_list);
            _personsSorterServiceMock.Setup(p => 
p.GetSortedPersons(It.IsAny<List<PersonResponse>>(), It.IsAny<string>(), 
It.IsAny<SortOrderOptions>())).ReturnsAsync(persons_response_list);

            PersonsController personsController = new 
PersonsController(_personsAdderService,_personsDeleterService,_personsGetterService,_personsSorterService,_personsUpdaterService, _countriesService, _logger);

            //Act
            IActionResult result = await 
personsController.Index(_fixture.Create<string>(), _fixture.Create<string>(), 
_fixture.Create<string>(), _fixture.Create<SortOrderOptions>());

            //Assert
            ViewResult viewResult = Assert.IsType<ViewResult>(result);
            
viewResult.ViewData.Model.Should().BeAssignableTo<IEnumerable<PersonResponse>>();
            viewResult.ViewData.Model.Should().Be(persons_response_list);
        }
        #endregion

        #region Create
        [Fact]
        public async Task Create_IfModelErrors_ToReturnCreateView()
        {
            //Arrange
            PersonAddRequest personAddRequest = 
_fixture.Create<PersonAddRequest>();
            PersonResponse personResponse = _fixture.Create<PersonResponse>();
            List<CountryResponse> countries = 
_fixture.Create<List<CountryResponse>>();

            _countriesServiceMock.Setup(c => c.GetAllCountries())
                .ReturnsAsync(countries);
            _personsAdderServiceMock.Setup(p => 
p.AddPerson(It.IsAny<PersonAddRequest>()))
                .ReturnsAsync(personResponse);

            PersonsController personsController = new 
PersonsController(_personsAdderService, _personsDeleterService, 
_personsGetterService, _personsSorterService, _personsUpdaterService, 
_countriesService, _logger);

            //Act
            personsController.ModelState.AddModelError("PersonName", "Person Name 
can't be blank");
            IActionResult result = await 
personsController.Create(personAddRequest);

            //Assert
            ViewResult viewResult = Assert.IsType<ViewResult>(result);
            
viewResult.ViewData.Model.Should().BeAssignableTo<PersonAddRequest>();
            viewResult.ViewData.Model.Should().Be(personAddRequest);
        }

        [Fact]
        public async Task Create_IfNoModelErrors_ToReturnRedirectToIndex()
        {
            //Arrange
            PersonAddRequest personAddRequest = 
_fixture.Create<PersonAddRequest>();
            PersonResponse personResponse = _fixture.Create<PersonResponse>();
            List<CountryResponse> countries = 
_fixture.Create<List<CountryResponse>>();

            _countriesServiceMock.Setup(c => c.GetAllCountries())
                .ReturnsAsync(countries);
            _personsAdderServiceMock.Setup(p => 
p.AddPerson(It.IsAny<PersonAddRequest>()))
                .ReturnsAsync(personResponse);

            PersonsController personsController = new 
PersonsController(_personsAdderService, _personsDeleterService, 
_personsGetterService, _personsSorterService, _personsUpdaterService, 
_countriesService, _logger);

            //Act
            IActionResult result = await 
personsController.Create(personAddRequest);

            //Assert
            RedirectToActionResult redirectToActionResult = 
Assert.IsType<RedirectToActionResult>(result);
            redirectToActionResult.ControllerName.Should().Be("Persons");
            redirectToActionResult.ActionName.Should().Be("Index");
        }
        #endregion

    }
}

Gitee获取源码:

https://gitee.com/huang_jianhua0101/asp.-net-core-8.git

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

黄健华Yeah

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值