0
[Route("api/[controller]")]
public class DigitalDocumentController : Controller
{

    private IDigitalDocumentService digitalDocumentService;
    private IDatabaseInitializer databaseInitializer;

    public DigitalDocumentController(IDigitalDocumentService digitalDocumentService)
    {
        this.digitalDocumentService = digitalDocumentService;
    }

    public DigitalDocumentController(IDatabaseInitializer databaseInitializer)
    {
        this.databaseInitializer = databaseInitializer;
    }

i want two controller constructor in my project to Mock in xUnit Testing, but there was an error in my swagger interface { "error": "Multiple constructors accepting all given argument types have been found in type 'i2ana.Web.Controllers.DigitalDocumentController'. There should only be one applicable constructor." } can anybody help me how i can do it ?

… what i am try to do , is to test Uniquness of the Name Field in my database My testing code:

[Fact]
public void AddNotUniqueName_ReturnsNotFoundObjectResult()
{
    var digitalDocument = new DigitalDocument
    {
        Image = new byte[] { 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20 },
             CreatedOn = DateTime.Today,
             Id = 6,
             Location = "temp",
             Name = "Flower",
             Tages = new List<Tag> { new Tag { Id = 1, Value = "Tag 1" }, new Tag { Id = 1, Value = "Tag 2" } }
    };
    // Arrange
    var mockRepo = new Mock<IDatabaseInitializer>();
    mockRepo.Setup(repo => repo.SeedAsync()).Returns(Task.FromResult(AddUniqueDigitalDocument(digitalDocument)));
    var controller = new DigitalDocumentController(mockRepo.Object);

    // Act
    var result = controller.Add(digitalDocument);

    // Assert
    var viewResult = Assert.IsType<NotFoundObjectResult>(result);
    var model = Assert.IsAssignableFrom<int>(viewResult.Value);
    Assert.NotEqual(6, model);
}

the "AddUniqueDigitalDocument" returns 6 only to test that the new digitaldocumet is not the same id of my initialize data.

1 Answer 1

3

When using dependency injection, you should only have one constructor where all dependencies can be satisfied. Otherwise, how is the DI container to know which constructor to utilize? That's your issue here. Using the Microsoft.Extensions.DependencyInjection package, and since this is a controller you're injecting into, there's only one reasonable way to solve this: don't register one or the other of the services, IDigitalDocumentService or IDatatabaseInitializer. If only one is registered, the service collection will simply use the constructor it has a registered service for.

It's possible with a more featured DI container, you might be able to configure something to allow it choose the proper constructor. How to do that would be entirely dependent on the DI container you end up going with, though, so not much more can be said on the subject at this point. Just realize that the default container (Microsoft.Extensions.DependencyInjection) is intentionally simplistic, so if you needs are more complex, you should sub in a full DI container.

UPDATE

You should be doing integration testing with the test host and an in-memory database. The basic approach is:

public MyTests()
{
    _server = new TestServer(new WebHostBuilder().UseStartup<TestStartup>());
    _context = _server.Host.Services.GetRequiredService<MyContext>();
    _client = _server.CreateClient();
 }

In your app's Startup, create a virtual method:

public virtual void ConfigureDatabase(IServiceCollection services)
{
    // normal database setup here, e.g.
    services.AddDbContext<MyContext>(o =>
        o.UseSqlServer(Configuration.GetConnectionString("Foo")));
}

Then, in ConfigureServices, replace your database setup with a call to this method.

Finally, in your test project, create a TestStartup class and override the ConfigureDatabase method:

public class TestStartup : Startup
{
    public override void ConfigureDatabase(IServiceCollection services)
    {
        var databaseName = Guid.NewGuid().ToString();
        services.AddDbContext<MyContext>(o =>
            o.UseInMemoryDatabase(databaseName));
    }
}

Now, in your tests you just make requests against the test client (which is just an HttpClient instance, so it works like any other HttpClient). You start by setting up your database with appropriate test data, and then ensure that the correct response is returned:

// Arrange
_context.Add(new DigitalDocument { Name = "Foo" });
await _context.SaveChanges();

// Act
// Submit a `DigitalDocument` with the same name via `_client`

// Assert
// Inspect the response body for some indication that it was considered invalid. Or you could simply assert that no new `DigitalDocument` was created by querying `_context` (or both)

This is admittedly a lot easier with an API, as with a web application, you're going to invariably need to do some HTML parsing. However, the docs and corresponding sample app help you with that.

Additionally, in actual practice, you'd want to use a test fixture to prevent having to bootstrap a test server for every test. Again, the docs have you covered there. One thing to note, though, is that once you switch to using a fixture, your database will then be persisted between tests. To segregate your test data, make sure that you call EnsureDeleted() on your context before each test. This can be easily done in the test class' constructor:

public class MyTests : IClassFixture<WebApplicationFactory<Startup>>
{
    private readonly HttpClient _client;
    private readonly MyContext _context;

    public MyTests(WebApplicationFactory<Startup> factory)
    {
        factory = factory.WithWebHostBuilder(builder => builder.UseStartup<TestStartup>());
        _client = factory.CreateClient();
        _context = factory.Server.Host.Services.GetRequiredService<MyContext>();
        _context.EnsureDeleted();
     }

I don't even like this much bootstrapping code in my tests, though, so I usually inherit from a fixture class instead:

public class TestServerFixture : IClassFixture<WebApplicationFactory<Startup>>
{
    protected readonly HttpClient _client;
    protected readonly MyContext _context;

    public TestServerFixture(WebApplicationFactory<Startup> factory)
    {
        factory = factory.WithWebHostBuilder(builder => builder.UseStartup<TestStartup>());
        _client = factory.CreateClient();
        _context = factory.Server.Host.Services.GetRequiredService<MyContext>();
        _context.EnsureDeleted();
     }
}

Then, for each test class:

 public class MyTests : TestServerFixture
 {
     public MyTests(WebApplicationFactory<Startup> factory)
         : base(factory)
     {
     }

This may seem like a lot, but most of it is one-time setup. Then, your tests will be much more accurate, more robust, and even easier in many ways.

Sign up to request clarification or add additional context in comments.

12 Comments

Thanx Alot for these informations can you give me an example to solve my problem, becuase i should use the dependency injection and the two services in my project How to configure something allow choosing constructor ?
Again, you're on your own there - at least initially. You'll need to pick an alternate container: Ninject, Autofac, etc., and consult the documentation for that. Get it integrated and attempt a solution. Then, if you have issues, still, you can come back and ask a specific question about that.
I really need help, because I'm a beginner in the asp.net core. i do a lot of searches but i cant understand how to do this :( can you give me a good reference to begin from it? sorry for annoying you !
Well, before you go down this road, the question of why you need two different constructors in the first place is probably worth asking. I didn't want to just avoid your question entirely with "you're doing it wrong", but is bears saying. What are you actually trying to achieve here?
i want to do test on API services"Get, Put, .." and another method "OnModelCreating" which check if the Name field in the database is unique. the test is on the "DigitalDocumentController"only ! and the "OnModelCreating" method in the ApplicationDbContext class. so i want to make two constructor from "DigitalDocumentController" for these two services(API and OnModelCreating method). that is what i am try to do
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.