|
| 1 | +# Expectations |
| 2 | + |
| 3 | +Attaching expectations to a test case is how the developer can express conditions on exit code, stdout, or stderr, |
| 4 | +to be verified for the test to pass. |
| 5 | + |
| 6 | +The simplest way to do that is to use the helper `test.Expects(exitCode int, errors []error, outputCompare test.Comparator)`. |
| 7 | + |
| 8 | +```go |
| 9 | +package main |
| 10 | + |
| 11 | +import ( |
| 12 | + "testing" |
| 13 | + |
| 14 | + "go.farcloser.world/tigron/test" |
| 15 | +) |
| 16 | + |
| 17 | +func TestMyThing(t *testing.T) { |
| 18 | + // Declare your test |
| 19 | + myTest := &test.Case{} |
| 20 | + |
| 21 | + // Attach a command to run |
| 22 | + myTest.Command = test.Custom("ls") |
| 23 | + |
| 24 | + // Set your expectations |
| 25 | + myTest.Expected = test.Expects(expect.ExitCodeSuccess, nil, nil) |
| 26 | + |
| 27 | + // Run it |
| 28 | + myTest.Run(t) |
| 29 | +} |
| 30 | +``` |
| 31 | + |
| 32 | +### Exit status expectations |
| 33 | + |
| 34 | +The first parameter, `exitCode` should be set to one of the provided `expect.ExitCodeXXX` constants: |
| 35 | +- `expect.ExitCodeSuccess`: validates that the command ran and exited successfully |
| 36 | +- `expect.ExitCodeTimeout`: validates that the command did time out |
| 37 | +- `expect.ExitCodeSignaled`: validates that the command received a signal |
| 38 | +- `expect.ExitCodeGenericFail`: validates that the command failed (failed to start, or returned a non-zero exit code) |
| 39 | +- `expect.ExitCodeNoCheck`: does not enforce any verification at all on the command |
| 40 | + |
| 41 | +... you may also pass explicit exit codes directly (> 0) if you want to precisely match them. |
| 42 | + |
| 43 | +### Stderr expectations with []error |
| 44 | + |
| 45 | +To validate that stderr contain specific information, you can pass a slice of `error` as `test.Expects` |
| 46 | +second parameter. |
| 47 | + |
| 48 | +The command output on stderr is then verified to contain all stringified errors. |
| 49 | + |
| 50 | +### Stdout expectations with Comparators |
| 51 | + |
| 52 | +The last parameter of `test.Expects` accepts a `test.Comparator`, which allows testing the content of the command |
| 53 | +output on `stdout`. |
| 54 | + |
| 55 | +The following ready-made `test.Comparator` generators are provided: |
| 56 | +- `expect.Contains(string)`: verifies that stdout contains the string parameter |
| 57 | +- `expect.DoesNotContain(string)`: negation of above |
| 58 | +- `expect.Equals(string)`: strict equality |
| 59 | +- `expect.Match(*regexp.Regexp)`: regexp matching |
| 60 | +- `expect.All(comparators ...Comparator)`: allows to bundle together a bunch of other comparators |
| 61 | +- `expect.JSON[T any](obj T, verifier func(T, string, tig.T))`: allows to verify the output is valid JSON and optionally |
| 62 | +pass `verifier(T, string, tig.T)` extra validation |
| 63 | + |
| 64 | +### A complete example |
| 65 | + |
| 66 | +```go |
| 67 | +package main |
| 68 | + |
| 69 | +import ( |
| 70 | + "testing" |
| 71 | + "errors" |
| 72 | + |
| 73 | + "go.farcloser.world/tigron/tig" |
| 74 | + "go.farcloser.world/tigron/test" |
| 75 | + "go.farcloser.world/tigron/expect" |
| 76 | +) |
| 77 | + |
| 78 | +type Thing struct { |
| 79 | + Name string |
| 80 | +} |
| 81 | + |
| 82 | +func TestMyThing(t *testing.T) { |
| 83 | + // Declare your test |
| 84 | + myTest := &test.Case{} |
| 85 | + |
| 86 | + // Attach a command to run |
| 87 | + myTest.Command = test.Custom("bash", "-c", "--", ">&2 echo thing; echo '{\"Name\": \"out\"}'; exit 42;") |
| 88 | + |
| 89 | + // Set your expectations |
| 90 | + myTest.Expected = test.Expects( |
| 91 | + expect.ExitCodeGenericFail, |
| 92 | + []error{errors.New("thing")}, |
| 93 | + expect.All( |
| 94 | + expect.Contains("out"), |
| 95 | + expect.DoesNotContain("something"), |
| 96 | + expect.JSON(&Thing{}, func(obj *Thing, info string, t tig.T) { |
| 97 | + assert.Equal(t, obj.Name, "something", info) |
| 98 | + }), |
| 99 | + ), |
| 100 | + ) |
| 101 | + |
| 102 | + // Run it |
| 103 | + myTest.Run(t) |
| 104 | +} |
| 105 | +``` |
| 106 | + |
| 107 | +### Custom stdout comparators |
| 108 | + |
| 109 | +If you need to implement more advanced verifications on stdout that the ready-made comparators can't do, |
| 110 | +you can implement your own custom `test.Comparator`. |
| 111 | + |
| 112 | +For example: |
| 113 | + |
| 114 | +```go |
| 115 | +package whatever |
| 116 | + |
| 117 | +import ( |
| 118 | + "testing" |
| 119 | + |
| 120 | + "gotest.tools/v3/assert" |
| 121 | + |
| 122 | + "go.farcloser.world/tigron/tig" |
| 123 | + "go.farcloser.world/tigron/test" |
| 124 | +) |
| 125 | + |
| 126 | +func TestMyThing(t *testing.T) { |
| 127 | + // Declare your test |
| 128 | + myTest := &test.Case{} |
| 129 | + |
| 130 | + // Attach a command to run |
| 131 | + myTest.Command = test.Custom("ls") |
| 132 | + |
| 133 | + // Set your expectations |
| 134 | + myTest.Expected = test.Expects(0, nil, func(stdout, info string, t tig.T){ |
| 135 | + t.Helper() |
| 136 | + // Bla bla, do whatever advanced stuff and some asserts |
| 137 | + }) |
| 138 | + |
| 139 | + // Run it |
| 140 | + myTest.Run(t) |
| 141 | +} |
| 142 | + |
| 143 | +// You can of course generalize your comparator into a generator if it is going to be useful repeatedly |
| 144 | + |
| 145 | +func MyComparatorGenerator(param1, param2 any) test.Comparator { |
| 146 | + return func(stdout, info string, t tig.T) { |
| 147 | + t.Helper() |
| 148 | + // Do your thing... |
| 149 | + // ... |
| 150 | + } |
| 151 | +} |
| 152 | + |
| 153 | +``` |
| 154 | + |
| 155 | +You can now pass along `MyComparator(comparisonString)` as the third parameter of `test.Expects`, or compose it with |
| 156 | +other comparators using `expect.All(MyComparator(comparisonString), OtherComparator(somethingElse))` |
| 157 | + |
| 158 | +Note that you have access to an opaque `info` string, that provides a brief formatted header message that assert |
| 159 | +will use in case of failure to provide context on the error. |
| 160 | +You may of course ignore it and write your own message. |
| 161 | + |
| 162 | +### Advanced expectations |
| 163 | + |
| 164 | +You may want to have expectations that contain a certain piece of data that is being used in the command or at |
| 165 | +other stages of your test (like `Setup` for example). |
| 166 | + |
| 167 | +To achieve that, you should write your own `test.Manager` instead of using the helper `test.Expects`. |
| 168 | + |
| 169 | +A manager is a simple function which only role is to return a `test.Expected` struct. |
| 170 | +The `test.Manager` signature makes available `test.Data` and `test.Helpers` to you. |
| 171 | + |
| 172 | +Here is an example, where we are using `data.Get("sometestdata")`. |
| 173 | + |
| 174 | +```go |
| 175 | +package main |
| 176 | + |
| 177 | +import ( |
| 178 | + "errors" |
| 179 | + "testing" |
| 180 | + |
| 181 | + "gotest.tools/v3/assert" |
| 182 | + |
| 183 | + "go.farcloser.world/tigron/test" |
| 184 | +) |
| 185 | + |
| 186 | +func TestMyThing(t *testing.T) { |
| 187 | + // Declare your test |
| 188 | + myTest := &test.Case{} |
| 189 | + |
| 190 | + myTest.Setup = func(data test.Data, helpers test.Helpers){ |
| 191 | + // Do things... |
| 192 | + // ... |
| 193 | + // Save this for later |
| 194 | + data.Set("something", "lalala") |
| 195 | + } |
| 196 | + |
| 197 | + // Attach a command to run |
| 198 | + myTest.Command = test.Custom("somecommand") |
| 199 | + |
| 200 | + // Set your fully custom expectations |
| 201 | + myTest.Expected = func(data test.Data, helpers test.Helpers) *test.Expected { |
| 202 | + // With a custom Manager you have access to both the test.Data and test.Helpers to perform more |
| 203 | + // refined verifications. |
| 204 | + return &test.Expected{ |
| 205 | + ExitCode: 1, |
| 206 | + Errors: []error{ |
| 207 | + errors.New("foobla"), |
| 208 | + }, |
| 209 | + Output: func(stdout, info string, t tig.T) { |
| 210 | + t.Helper() |
| 211 | + |
| 212 | + // Retrieve the data that was set during the Setup phase. |
| 213 | + assert.Assert(t, stdout == data.Get("sometestdata"), info) |
| 214 | + }, |
| 215 | + } |
| 216 | + } |
| 217 | + |
| 218 | + // Run it |
| 219 | + myTest.Run(t) |
| 220 | +} |
| 221 | +``` |
0 commit comments