how to unit test asp.net core application with constructor dependency injection

后端 未结 5 1305
难免孤独
难免孤独 2020-12-07 10:43

I have a asp.net core application that uses dependency injection defined in the startup.cs class of the application:

    public void ConfigureServices(ISer         


        
相关标签:
5条回答
  • 2020-12-07 10:51

    Although @Kritner's answer is correct, I prefer the following for code integrity and better DI experience:

    [TestClass]
    public class MatchRepositoryTests
    {
        private readonly IMatchRepository matchRepository;
    
        public MatchRepositoryTests()
        {
            var services = new ServiceCollection();
            services.AddTransient<IMatchRepository, MatchRepositoryStub>();
    
            var serviceProvider = services.BuildServiceProvider();
    
            matchRepository = serviceProvider.GetService<IMatchRepository>();
        }
    }
    
    0 讨论(0)
  • 2020-12-07 10:54

    If you are using the Program.cs + Startup.cs convention and want to get this working quickly you can reuse your existing host builder with a one-liner:

    using MyWebProjectNamespace;
    
    public class MyTests
    {
        readonly IServiceProvider _services = 
            Program.CreateHostBuilder(new string[] { }).Build().Services; // one liner
    
        [Test]
        public void GetMyTest()
        {
            var myService = _services.GetRequiredService<IMyService>();
            Assert.IsNotNull(myService);
        }
    }
    

    Sample Program.cs file from web project:

    using Microsoft.AspNetCore.Hosting;
    using Microsoft.Extensions.Hosting;
    
    namespace MyWebProjectNamespace
    {
        public class Program
        {
            public static void Main(string[] args) =>
                CreateHostBuilder(args).Build().Run();
    
            public static IHostBuilder CreateHostBuilder(string[] args) =>
                Host.CreateDefaultBuilder(args)
                    .ConfigureWebHostDefaults(webBuilder =>
                    {
                        webBuilder.UseStartup<Startup>();
                    });
        }
    }
    
    0 讨论(0)
  • 2020-12-07 11:01

    A simple way, I wrote a generic dependency resolver helper class and then built the IWebHost in my unit test class.

    Generic Dependency Resolver

        public class DependencyResolverHelpercs
        {
            private readonly IWebHost _webHost;
    
            /// <inheritdoc />
            public DependencyResolverHelpercs(IWebHost WebHost) => _webHost = WebHost;
    
            public T GetService<T>()
            {
                using (var serviceScope = _webHost.Services.CreateScope())
                {
                    var services = serviceScope.ServiceProvider;
                    try
                    {
                        var scopedService = services.GetRequiredService<T>();
                        return scopedService;
                    }
                    catch (Exception e)
                    {
                        Console.WriteLine(e);
                        throw;
                    }
                };
            }
        }
    }
    

    Unit Test Project

      [TestFixture]
        public class DependencyResolverTests
        {
            private DependencyResolverHelpercs _serviceProvider;
    
            public DependencyResolverTests()
            {
    
                var webHost = WebHost.CreateDefaultBuilder()
                    .UseStartup<Startup>()
                    .Build();
                _serviceProvider = new DependencyResolverHelpercs(webHost);
            }
    
            [Test]
            public void Service_Should_Get_Resolved()
            {
    
                //Act
                var YourService = _serviceProvider.GetService<IYourService>();
    
                //Assert
                Assert.IsNotNull(YourService);
            }
    
    
        }
    
    0 讨论(0)
  • 2020-12-07 11:01

    Why would you want to inject those in a test class? You would usually test the MatchController, for example, by using a tool like RhinoMocks to create stubs or mocks. Here's an example using that and MSTest, from which you can extrapolate:

    [TestClass]
    public class MatchControllerTests
    {
        private readonly MatchController _sut;
        private readonly IMatchService _matchService;
    
        public MatchControllerTests()
        {
            _matchService = MockRepository.GenerateMock<IMatchService>();
            _sut = new ProductController(_matchService);
        }
    
        [TestMethod]
        public void DoSomething_WithCertainParameters_ShouldDoSomething()
        {
            _matchService
                   .Expect(x => x.GetMatches(Arg<string>.Is.Anything))
                   .Return(new []{new Match()});
    
            _sut.DoSomething();
    
            _matchService.AssertWasCalled(x => x.GetMatches(Arg<string>.Is.Anything);
        }
    
    0 讨论(0)
  • 2020-12-07 11:02

    Your controllers in .net core have dependency injection in mind from the start, but this does not mean you are required to use a dependency injection container.

    Given a simpler class like:

    public class MyController : Controller
    {
    
        private readonly IMyInterface _myInterface;
    
        public MyController(IMyInterface myInterface)
        {
            _myInterface = myInterface;
        }
    
        public JsonResult Get()
        {
            return Json(_myInterface.Get());
        }
    }
    
    public interface IMyInterface
    {
        IEnumerable<MyObject> Get();
    }
    
    public class MyClass : IMyInterface
    {
        public IEnumerable<MyObject> Get()
        {
            // implementation
        }
    }
    

    So in your app, you're using the dependency injection container in your startup.cs, which does nothing more than provide a concretion of MyClass to use when IMyInterface is encountered. This does not mean it is the only way of getting instances of MyController however.

    In a unit testing scenario, you can (and should) provide your own implementation (or mock/stub/fake) of IMyInterface as so:

    public class MyTestClass : IMyInterface
    {
        public IEnumerable<MyObject> Get()
        {
            List<MyObject> list = new List<MyObject>();
            // populate list
            return list;
        }        
    }
    

    and in your test:

    [TestClass]
    public class MyControllerTests
    {
    
        MyController _systemUnderTest;
        IMyInterface _myInterface;
    
        [TestInitialize]
        public void Setup()
        {
            _myInterface = new MyTestClass();
            _systemUnderTest = new MyController(_myInterface);
        }
    
    }
    

    So for the scope of unit testing MyController, the actual implementation of IMyInterface does not matter (and should not matter), only the interface itself matters. We have provided a "fake" implementation of IMyInterface through MyTestClass, but you could also do this with a mock like through Moq or RhinoMocks.

    Bottom line, you do not actually need the dependency injection container to accomplish your tests, only a separate, controllable, implementation/mock/stub/fake of your tested classes dependencies.

    0 讨论(0)
提交回复
热议问题