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?
BartoszKP
  • 34,786
  • 15
  • 102
  • 130
SuperJMN
  • 13,110
  • 16
  • 86
  • 185
  • Why the wrapper? Why not just `return context.Users.First()` (or even just `return user;`)? – Camilo Terevinto Apr 19 '18 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 :) – SuperJMN Apr 19 '18 at 16:37
  • I don't see anything bad in this practice (for unit tests). – Evk Apr 19 '18 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. – ssmith Apr 19 '18 at 16:50
  • Alternately specify the Id. EF will store it fine when using InMemory. Store the IDs as constants tests can access. – ssmith Apr 19 '18 at 16:51

2 Answers2

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.

ssmith
  • 8,092
  • 6
  • 52
  • 93
  • This will have one small caveat - tests will run against same values. – Fabio Apr 19 '18 at 22:41
  • I've hit this problem :( https://stackoverflow.com/questions/29632627/the-property-name-is-part-of-the-objects-key-information-and-cannot-be-modifi – SuperJMN Apr 20 '18 at 08:01
  • 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. – ssmith Apr 20 '18 at 11:26
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.

BartoszKP
  • 34,786
  • 15
  • 102
  • 130