Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 470
Dilshod K Avatar asked Feb 12 '19 05:02

Dilshod K


People also ask

Does TestInitialize run for each test?

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, decorate relevant methods with the ClassInitialize and ClassCleanup attributes.

Which methods are used in test class to initialize and release common objects?

You declare these objects as a private variable, and initialize them by overriding the setUp() or via the constructor. You can perform clean-up operations by overriding tearDown() . Each test method runs on its own TestCase instance with its own set of text fixtures.

What is test initialization?

TestInitialize. This attribute is needed when we want to run a function before execution of a test. For example we want to run the same test 5 times and want to set some property value before running each time. In this scenario we can define one function and decorate the function with a TestInitialize attribute.


1 Answers

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.

like image 148
LukaszBalazy Avatar answered Oct 29 '22 09:10

LukaszBalazy