Dilshod K
Dilshod K

Reputation: 3032

How to get test initialize in memory and use in each test

I'm trying to create Unit Test. I have class User:

 public class User
{
    public int UsersCount
    {
        get
        {
            using (MainContext context = new MainContext())
            {
                return context.Users.Count();
            }
        }
    }
    public Guid Id { get; set; } = Guid.NewGuid();
    public string UserName { get; set; }
    public string Password { get; set; }
    public Contact UserContact { get; set; }
}

My first test is UsersCount_Test test which tests UsersCount property:

 [TestMethod]
    public void UsersCount_Test()
    {
        var user = new User();
        var context = new MainContext();
        int usersCount = context.Users.Count();
        context.Users.Add(new User());
        context.SaveChanges();
        Assert.AreEqual(usersCount + 1, user.UsersCount, $"It should be {usersCount + 1} because we're adding one more user");
    }

If I add new test method in my test class (I'm using separate classes for testing each entity), I need to create new instance of User. That's why I did this:

    public class BaseTest<T>
{
    public T TestEntity;

    public MainContext TestContext = new MainContext();
}

Now each test classes inherits from this class. And also I created test initializer method. Now my test class looks like this :

 [TestClass]
public class UserTest : BaseTest<User>
{
    [TestMethod]
    public void UsersCount()
    {
        int usersCount = TestContext.Users.Count();
        TestContext.Users.Add(new User());
        TestContext.SaveChanges();
        Assert.AreEqual(usersCount + 1, TestEntity.UsersCount, $"It should be {usersCount + 1} because we're adding one more user");
    }

    [TestInitialize]
    public void SetTestEntity()
    {
        TestEntity = new User();
    }
}

Now I'm adding new property to User and writing some logic:

  string phoneNumber;
    public string PhoneNumber { get { return phoneNumber; } set { SetUserContact(phoneNumber, value); phoneNumber = value; } }

    void SetUserContact(string oldContact, string newContact)
    {
        UserContact.ContactsList.Remove(oldContact);
        UserContact.ContactsList.Add(newContact);
    }

After that I'm creating new test :

     [TestMethod]
    public void ContactList_Test()
    {
        var newPhone = "+8888888888888";
        TestEntity.PhoneNumber = newPhone;
        Assert.IsTrue(TestEntity.UserContact.ContactsList.Any(a => a == newPhone), $"It should contains {newPhone}");
    }

Test fails because UserContact of TestEntity is null. I understood that TestEntity should be created by logic. After that I fix test initilizer method:

 [TestInitialize]
    public void SetTestEntity()
    {
        TestEntity = new User() { UserContact = new Contact() };
    }

Here is Contact model

    public class Contact
{
    public Guid Id { get; set; } = Guid.NewGuid();
    public virtual List<string> ContactsList { get; set; } = new List<string>();
}

My question is how to set TestEntity only one time, is it possible (maybe get it in memory and use it when it calls SetTestEntity method)? Because SetTestentity method creates a new entity in each test and it takes more development time. (For example, If creating an instance of UserContact takes 3 seconds all, test runs more than 3 seconds). Another way, in this case, is to set UserContact in ContactLists test, but I think it's not a good idea. In the future when we will add new logics, I need to fix each test. Please give me any suggestion and/or ideas.

Upvotes: 8

Views: 2425

Answers (2)

Mohit Verma
Mohit Verma

Reputation: 5294

TestInitialize and TestCleanup are ran before and after each test, this is to ensure that no tests are coupled.

If you want to run methods before and after ALL tests only once, decorate relevant methods with the ClassInitialize and ClassCleanup attributes.

You can use the following additional attributes as you write your tests:

Sample code-

// Use ClassInitialize to run code before running the first test in the class
[ClassInitialize()]
public static void MyClassInitialize(TestContext testContext) { }

// Use ClassCleanup to run code after all tests in a class have run
[ClassCleanup()]
public static void MyClassCleanup() { }

// Use TestInitialize to run code before running each test 
[TestInitialize()]
public void MyTestInitialize() { }

// Use TestCleanup to run code after each test has run
[TestCleanup()]
public void MyTestCleanup() { }

Basically you can have your SetEntity method in your ClassIntialize method Hope it helps.

Upvotes: 0

LukaszBalazy
LukaszBalazy

Reputation: 577

If you would really have to TestInitialize runs before each test. You could use ClassInitialize to run test initialization for class only once.

BUT

From what I'm seeing your performance issue is caused by desing and architecutre of your application where you are breaking single responsibility principle. Creating static database entity or sharing it across test is not a solution it is only creating more technical debt. Once you share anything across test it has to be maintained acorss test AND by definition unit test SHOULD run separately and independently to allow testing each scenarion with fresh data.

You shouldn't be creating database models that depend on MainContext. Should single User really know how many Users there are in the database? If not then please create separate repository that will have MainContext injected and method GetUsersCount() and unit test that with InMemoryDatabase by adding few users calling specific implementation and checking if correct number of users has been added, like following:

public interface IUsersRepository
    {
        int GetUsersCount();
    }

    public class UsersRepository : IUsersRepository
    {
        private readonly EntityFrameworkContext _context;

        public UsersRepository(EntityFrameworkContext context)
        {
            _context = context;
        }

        public int GetUsersCount()
        {
            return _context.Users.Count();
        }
    }

Later only methods that are really using context should be tested withInMemoryDatabase and for methods that are making use of IUserRepository each specific method should be mocked since it is tested separatly.

Upvotes: 5

Related Questions