How you set up test data is important, and I'd argue both versions are suboptimal.
Every method or class you write has a contract, whether explicit (by documentation for instance) or implicit (by what the code actually does). This contract is important, because it describes what the clients, i.e. the code that uses your class, should expect when it uses it. Unit testing is a method to programmatically document the contract of the code under test, in a way that it ensures that the behaviour is the same even if the implementation changes.
An important characteristic of unit tests is that they want the code under test (CUT) to be isolated from other code. This means you have to be very careful when the CUT has dependencies. If it uses a dependency which has a different reason to change than itself (this is what responsibility means in the single responsibility principle), you'll usually have an abstraction to isolate these two. This abstraction itself has a contract, and in unit testing, you assume that the abstraction on which you depend will behave according to its contract. In unit tests, this generally means that this dependency will be mocked, and you will pilot the mock to behave in a certain way.
Back to your example now. You are unit testing the UserLogic
class. It has two dependencies that I can see: User
and DbContext
. I don't have enough context to know what User
is, but I'll assume it is some sort of value object. In that case, it is fine to use it directly.
DbContext
is a different beast. It seems to be an implementation of some sort of persistence. It definitely has a different reason to change than UserLogic
, which means it should be abstracted by an interface of some sort. I'll assume you already have one which is called Context
.
Therefore, I can assume the implementation of UserLogic.AddUser
looks like something like this:
public boolean AddUser(User user) {
if (context.HasUser(user)) {
return false;
}
context.AddUser(user);
context.SaveChanges();
return true;
}
The outcome that you want to unit test is as follow: If the User
has already been added to the context
, you want to ensure the Context
hasn't changed (no new users were added, nor were the changes saved).
The description of the outcome described pretty much exactly how the unit test should look. What you want to arrange is that the context
already has a specific User
. What you want to act on is AddUser
. What you want to assert is that the User
was not added, and the Context
was not saved. Therefore, your unit test looks like this (in Java, I'm not too familiar with C# testing libraries):
@Test
public void givenContextAlreadyHasTheUser_whenAddUser_thenTheUserIsNotAddedASecondTime() {
// Arrange
Context context = mock(Context.class);
User user = new User("test@test.com");
UserLogic userLogic = new UserLogic(context);
given(context.HasUser(user)).willReturn(true);
// Act
userLogic.AddUser(user);
// Assert
verify(context, never()).AddUser(any());
verify(context, never()).SaveChanges();
}
As a user of the UserLogic
class, I can refer to this test to know exactly what the contract of AddUser
describes in the case where the User
is already added to the context, which is what I'm looking for in unit tests.
addUser
is compromised, it will compromise the whole test.