It may be useful to flip the arguments to std.testing.expectEqual. Right now, the expected value is the first formal parameter and the actual value is the second formal parameter.
When working with optional types, this makes testing via expectEqual impossible:
const std = @import("std");
pub fn expectEqual(actual: var, expected: @TypeOf(actual)) void {}
pub fn main() void {
const x: ?u8 = 1;
expectEqual(x, 1);
// Doesn't work as the type of the expected value is comptime_int
// std.testing.expectEqual(1, x);
const y: ?u8 = null;
expectEqual(x, null);
// Doesn't work as the type of the expected value is (null)
// std.testing.expectEqual(null, y);
}
But this may also be a very minor edge case.
An alternative would be to create a function in std.testing to handle optional types.
Right now, the expected value is the first formal parameter and the actual value is the second formal parameter.
This is expected so that actual can be coerced to expected. It's also a common pattern for testing frameworks, see e.g. http://olivinelabs.com/busted/#assert-equals
const y: ?u8 = null; expectEqual(x, null); // Doesn't work as the type of the expected value is (null) // std.testing.expectEqual(null, y);
Write: td.testing.expectEqual(@as(?u8, null), y);
Ok, when other testing frameworks (Junit, NUnit, etc.) also follow these guidelines, it should be ok to just explicitly cast the expected values to the correct type.
I actually favor this proposal. Adding those extra coercions is pretty gross and imo negatively affects readability. So either we're stuck with the correct but clunky expectEqual(@as(?u8, null), y) or we get by with the better looking but wrong expectEqual(y, null)
Alternately, we could see about coercing the literal to the other type if possible.
I did helpers in my zigimg library to reserve the order of expected and actual because it annoyed me so much: https://github.com/mlarouche/zigimg/blob/master/tests/helpers.zig
I'll reopen this issue to because there seems some support for this.
I just noticed I do it the wrong way around in all my test code. 馃槓
Because I read it like this: _expect x to be 1_ and not _expect 1 to be x_ I guess.
It's not just with optional types this is annoying. For example:
const std = @import("std");
test "math" {
const num: f64 = 1.5;
std.testing.expectEqual(1.0, std.math.floor(num));
}
Which leads to this error:
./test.zig:5:48: error: expected type 'comptime_float', found 'f64'
std.testing.expectEqual(1.0, std.math.floor(num));
^
./test.zig:5:28: note: referenced here
std.testing.expectEqual(1.0, std.math.floor(num));
^
As has been pointed out, this can be fixed with an explicit cast, which is ugly, or by swapping the arguments, which leads to the wrong error messages when a test fails.
Because testing is so easy to do in Zig, and because all the documentation uses tests to demonstrate concepts, new users are quite likely to use them. It may not be obvious to them that the correct solution is to cast the expected value. When I first encountered it, I "solved" the problem by swapping the arguments, which works to catch failing tests, but leads to confusing error messages.
Another solution would be to make the type explicit, so it would look like:
std.testing.expectEqual(f32, 1.0, std.math.floor(num));
I ended up with this wrapper as a workaround, which preserves the intended parameter order and doesn't clutter the parameter list:
const testing = @import("std").testing;
pub fn expectEqual(expected: anytype, actual: anytype) void {
testing.expectEqual(@as(@TypeOf(actual), expected), actual);
}
test "expectEqual correctly coerces types that std.testing.expectEqual does not" {
const int_value: u8 = 2;
expectEqual(2, int_value);
const optional_value: ?u8 = null;
expectEqual(null, optional_value);
const Enum = enum { One, Two };
const enum_value = Enum.One;
expectEqual(.One, enum_value);
}
Most helpful comment
I just noticed I do it the wrong way around in all my test code. 馃槓
Because I read it like this: _expect x to be 1_ and not _expect 1 to be x_ I guess.