简体   繁体   中英

EF Moq Unit Test, unsure verify

I am totally new to unit tests. I read many "tutorials" from different people and I decided to use msdn solutions.

I use this https://msdn.microsoft.com/en-us/library/dn314429(v=vs.113).aspx as my test, I am interested in " Testing non-query scenarios " there.

According to this article, I tried to test my simple CRUD's Create() action.

Here's my code ( FinancialAssistantEntities is my DbContext (EF Database First)):

Context:

public partial class FinancialAssistantEntities : DbContext
{
    public FinancialAssistantEntities()
        : base("name=FinancialAssistantEntities")
    {
    }
    .
    .
    .
    public virtual DbSet<FAWallet> FAWallet { get; set; }
}

Repository method: (I commented out my transaction's using , because running it from test method causes the error "No connection string named 'FinancialAssistantEntities' could be found in the application config file."),

public async Task<bool> CreateWallet(FAWallet model)
{
    using (var context = Context)
    {
        // transaction with IsolationLevel
        //using (var tran = context.Database.BeginTransaction(IsolationLevel.ReadUncommitted))
        {
            try
            {                       
                context.FAWallet.Add(model);
                //context.SaveChanges();
                await context.SaveChangesAsync();
                //tran.Commit();
                return true;
            }
            catch (Exception ex)
            {
                //tran.Rollback();
                throw ex;
            }
        }
    }
}

TestMethod:

[TestMethod]
public void CreateWalletTest()
{
    var wallet = new FAWallet()
    {
        WalletId = Guid.NewGuid(),
        //WalletName = StringHelper.GenerateRandomString(12),
        // admin ID
        WalletUserId = "e6888245-1d9b-431c-a068-aa62932e47ec",
        WalletCreateDate = DateTime.Now,
        WalletEnabled = true
    };

    var mockSet = new Mock<DbSet<FAWallet>>();

    var mockContext = new Mock<FinancialAssistantEntities>();
    mockContext.Setup(x => x.FAWallet).Returns(mockSet.Object);

    var walletRepository = new FAWalletRepository(mockContext.Object);
    walletRepository.CreateWallet(wallet).Wait();

    mockSet.Verify(x => x.Add(It.IsAny<FAWallet>()), Times.Once());
    mockContext.Verify(x => x.SaveChangesAsync(), Times.Once()); 
}

First of all, I don't know if commenting transaction's using out is good idea, though I don't know much about testing yet.

Secondly, my test always passes. I even commented out the WalletName property's set, since this field is not-nullable, so it seems I made sth wrong.

Foreword

Before we start off with examining the bits and pieces of your question, let me be clear that the main problem is not about unit testing. Instead, it's about Object Oriented Programming and some of analysis.

Analyzing the problem space

Let's see what you wrote in a comment:

I've read similar answers like yours under other threads, but I mean testing my mistakes, for example. I have some objects filled by user only partially, after that system automatically fills other fields, for ex. create date, create user, etc. When I miss filling some of these fields, SaveChanges() would throw me an error.

You are approaching this task from the wrong angle.

Why do I say this? Because:

  1. You are using Entity Framework ORM ( EF ) persistence model as the source of behavior , the model held responsible for the given business interaction

  2. You want EF to this kind of validation

  3. You want to test all this through EF mechanism. You are testing the wrong thing

Addressing the issues

What you really want to do is tie your model into EF in its very heart. Which is not good because:

  • You are tightly coupling your code to EF, with unnecessary dependencies
  • It makes testing your business logic hard and slow
  • The business logic is one of most important and valuable parts of your code, something you (in ideal conditions) get paid for in the end

Now let's focus on the first three points above in detail.

First: I'm strongly advising you to create an object which has the more less dependencies possible. Let's call it an entity for the sake of the example, which will contain all the needed behavior encapsulated. Like you have mentioned; have public methods to set properties and other invariants.

Second: You can also have all the validations needed to guard this type and all of its invariants. The common place for such validation can be the constructor or any public method accepting and validating arguments it receives. In case of errors you can just throw your custom business exceptions and assert against these in your tests.

These all combined, into one object, is called; cohesion .

Third: now that you have a cleaner object, which models the given business interaction, you now just need to test this code in total isolation. Which is a nice thing to have, because it's fast, it's focused and does not load hordes of dependencies (compared to integration testing with EF).


All's Well That Ends Well

Of course like with everything, all this comes with a price. And that is when you separate something from the system you might be introducing another layer of indirection. And this is that you now need to map your "domain model" to an EF persistence model and vice versa.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM