How to Write a Unit Test

New to unit testing, how to write great tests?


My tests just seems so tightly bound to the method (testing all codepath, expecting some inner methods to be called a number of times, with certain arguments), that it seems that if I ever refactor the method, the tests will fail even if the final behavior of the method did not change.

I think you are doing it wrong.

A unit test should:

  • test one method
  • provide some specific arguments to that method
  • test that the result is as expected

It should not look inside the method to see what it is doing, so changing the internals should not cause the test to fail. You should not directly test that private methods are being called. If you are interested in finding out whether your private code is being tested then use a code coverage tool. But don't get obsessed by this: 100% coverage is not a requirement.

If your method calls public methods in other classes, and these calls are guaranteed by your interface, then you can test that these calls are being made by using a mocking framework.

You should not use the method itself (or any of the internal code it uses) to generate the expected result dynamically. The expected result should be hard-coded into your test case so that it does not change when the implementation changes. Here's a simplified example of what a unit test should do:

testAdd()
{
int x = 5;
int y = -2;
int expectedResult = 3;
Calculator calculator = new Calculator();
int actualResult = calculator.Add(x, y);
Assert.AreEqual(expectedResult, actualResult);
}

Note that how the result is calculated is not checked - only that the result is correct. Keep adding more and more simple test cases like the above until you have have covered as many scenarios as possible. Use your code coverage tool to see if you have missed any interesting paths.

How to write a unit test for a converter?

You'll need to construct two objects - The input object, and an object which matches the result that you want to get. Then, run the conversion method on the input object, and check that it matches your expected result.

One caveat: in order for the expect function to properly compare the two objects for equality, you'll need to either override the == operator for Group and GroupDBData, or use the equatable package (which does the same thing with less hassle).

For example (guessing at some of the details):

// group_db_data.dart

class GroupDBData extends Equatable {
final int id;
final String groupName;

GroupDBData({
required this.id,
required this.groupName,
});

@override
List<Object> get props => [id, groupName];
}
// group.dart

class Group extends Equatable {
final int id;
final String groupName;

Group({
required this.id,
required this.groupName,
});

@override
List<Object> get props => [id, groupName];
}

Finally, to perform the tests:

main() {
group('Converter', () {
test('The result should be a database model converted to a local model',
() {
final groupConverter = GroupDbConverter();
final model = GroupDBData(id: 132, groupName: "Test Group");
final expected = Group(id: 132, groupName: "Test Group");

final actual = groupConverter.processConvertInToOut(model);

expect(actual, expected);
});

test('The result should be a local model converted to a database model',
() {
final groupConverter = GroupDbConverter();
final group = Group(id: 132, groupName: "Test Group");
final expected = GroupDBData(id: 132, groupName: "Test Group");

final actual = groupConverter.processConvertOutToIn(group);

expect(actual, expected);
});
});
}

Writing unit test with JUnit and Mockito

I will try to answer some of the questions that you have asked:

How many tests should I write for this method?: Essentially while writing test cases for a function or piece of code, there is no limit on how many test cases you should write. Generally you try to test atleast 1 positive flow and 1 negative flow. If your piece of code has many edge cases, then probably check those as well.

Do I need to test stream(I don't even know how)? : No. Stream is inbuilt java feature, you don't test inbuilt features. You just test your logic.

How can you decide what to test in a method?: As I mentioned earlier, you generally test positive flows and negative flows including any edge cases. For the above function it seems you have 1 positive and 1 negative flow. Your positive scenario could be where you expect value to not be present in your database and successfully store the data and your negative scenario can be when that data is already present you throw an exception. These are two minimum test cases you should write.

Why should I write unit tests when I can use Postman and debugger to check my code?

This isn't a well-suited place to answer such as wide question, but I'll give a few pointers that you can use to dig further into testing and it's gains.

There are many more goals of tests than to just verify that a test returned the same thing you verified with postman or what you saw in your debugger (you can also write test suites in postman) at that time.

The most obvious feature comes the next time you change any code that should or should not touch what you verified manually earlier - suddenly you get verification that the old code still works in the same way as it did when you wrote it. The behavior hasn't changed because of what you did this time, and any code relying on the old behavior should still work. Unless you have automated tests you can't really be sure that your recent changes hasn't changed existing behavior.

This creates a safety net that allows you break less things as the project grows.

Having code that is testable also forces separation of concerns for the interface being tested (HTTP/interface/class/method/etc.).

These tests will also work as simple documentation of how your API is supposed to be used for new developers, and allows new developers to be more confident in their changes to the project.

Writing tests should also make you think "what are the edge cases here?" and write tests that expose those edge cases and verify that they work as expected.

You still test your code when you do it manually, so instead of just making that knowledge disappear when you're finished with a piece of code, formalize it as tests and make it available for the future as well.

There will always be a slight overhead of writing tests - in particular learning a test framework, how you test, and how you write code that is testable, but as with everything else, as soon as you get into the flow, it becomes second nature.



Related Topics



Leave a reply



Submit