1

I'm currently testing an Entity Framework's DbContext using the In-Memory Database.

In order to make tests as atomic as possible, the DbContext is unique per test-method, and it's populated with initial data needed by each test.

To set the initial state of the DbContext, I've created a void SetupData method that fills the context with some entities that I will use in the tests.

The problem with this approach is that the objects that are created during the setup cannot be accessed by the test, because Entity Framework will assign the Ids itself, that are unknown until run-time.

To overcome this problem, I've thought that my SetupData method could become something like this:

public Fixture SetupData(MyContext context) 
{
    var fixture = new Fixture();
    fixture.CreatedUser = new User();
    context.Users.Add(fixture.CreatedUser);
    context.SaveChanges();

    return fixture;
}

public class Fixture 
{
    public User CreatedUser { get; set;}
}

As you see, it's returning an instance of what I called "Fixture". (I don't know if the name fits well).

This way, the SetupData will return an object (Fixture) with references to the entities. Thus, the test can use the created object. Otherwise, the object will be impossible to identify, since the Id isn't created until the SaveChanges is called.

My question is:

  • Is this a bad practice?
  • Is there a better way to reference initial data?
5
  • Why the wrapper? Why not just return context.Users.First() (or even just return user;)? Commented Apr 19, 2018 at 16:29
  • @CamiloTerevinto It's because the setup will not only return one entity, but a lot of them that could be interesting to make the tests. For instance, it can have a ExistingUser, a SoftDeletedUser, and and UserWithNegativeBalance. I hope you understand what I'm saying :) Commented Apr 19, 2018 at 16:37
  • I don't see anything bad in this practice (for unit tests). Commented Apr 19, 2018 at 16:48
  • Give each entity a name or something other than I’d you can use to look it up if necessary. Store them as constants your tests and test setup code can both use. Commented Apr 19, 2018 at 16:50
  • Alternately specify the Id. EF will store it fine when using InMemory. Store the IDs as constants tests can access. Commented Apr 19, 2018 at 16:51

2 Answers 2

2

I prefer this approach:

public void SetupData(MyContext context) 
{
    var user = new User() { Id = Fixture.TEST_USER1_ID, UserName = Fixture.TEST_USER1_NAME };
    context.Users.Add(user);
    context.SaveChanges();
}

public class Fixture 
{
    public const int TEST_USER1_ID = 123;
    public const string TEST_USER!_NAME = "testuser";
}

Your approach is probably fine, too, but you probably will want to know the user ID somewhere in your tests and this makes it very easy to specify it in a single known location so that it won't change if for instance you later on change your test data and the order in which you add users.

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

3 Comments

This will have one small caveat - tests will run against same values.
What problem exactly? You don’t have a ‘name’ property in your example so what property is EF complaining about? My code isn’t showing modification of any records that are already in the database, only new records.
1

This is not a bad practice. In fact it is a good approach to create readable Given-When-Then tests. If you consider:

  • splitting your SetupData method
  • renaming it
  • possibly changing to a extension method
public static MyContextExtensions
{
    public static User Given(this MyContext @this, User user)
    {
        @this.Users.Add(user);
        @this.SaveChanges();

        return user;
    }

    public static OtherEntity Given(this MyContext @this, OtherEntity otherEntity)
    {
         // ...
    }

    // ...
}

you can then write (a conceptual example, details need to be reworked to match your implementation):

[Test]
public GivenAUser_WhenSearchingById_ReturnsTheUser()
{
    var expectedUsername = "username";
    var user = _context.Given(AUser.WithName(expectedUsername));

    var result = _repository.GetUser(user.Id);

    Assert.That(result.Name, Is.EqualTo(expectedUsername));
}

... and similarly for other entities.

Comments

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.