https://github.com/jimschubert/mimic
mimic - testing terminal interactions
https://github.com/jimschubert/mimic
Last synced: over 1 year ago
JSON representation
mimic - testing terminal interactions
- Host: GitHub
- URL: https://github.com/jimschubert/mimic
- Owner: jimschubert
- License: apache-2.0
- Created: 2022-11-13T22:59:16.000Z (over 3 years ago)
- Default Branch: main
- Last Pushed: 2022-11-27T17:05:03.000Z (over 3 years ago)
- Last Synced: 2025-02-15T05:46:20.619Z (over 1 year ago)
- Language: Go
- Homepage: https://pkg.go.dev/github.com/jimschubert/mimic
- Size: 41 KB
- Stars: 3
- Watchers: 3
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# mimic - testing terminal interactions
[](#installing)
[](https://pkg.go.dev/github.com/jimschubert/mimic)

[](./LICENSE)
[](https://github.com/jimschubert/mimic/actions/workflows/build.yml)
[](https://goreportcard.com/report/github.com/jimschubert/mimic)
Mimic aims to provide a simple and clean contract for interacting with terminal contents while unit testing.
The idea arose from use of [go-survey/survey](https://github.com/go-survey/survey) and interest in using something like [charmbracelet/vhs](https://github.com/charmbracelet/vhs) for integration testing of a CLI.
A well-crafted applications can use mimic for integration testing of CLIs with full capability to mock dependencies. This differs from integration testing with tools like vhc because mimic runs in-process.
A goal of this project is to simplify testing CLIs using `survey` by providing a test suite which reduces test boilerplate and adheres to testify's suite interface(s).
Mimic also provides commands for:
* checking existence of strings in the output buffer
* checking existence of patterns in the output buffer
* checking formatted output according to pseudoterminal configuration
## Installing
First, get the latest version of mimic:
```shell
go get -u github.com/jimschubert/mimic@latest
```
Then, import mimic:
```go
import "github.com/jimschubert/mimic"
```
## Usage
Refer to [documentation](https://godoc.org/github.com/jimschubert/mimic) for general API and usage.
For a real-world example, see [suite_test.go](./suite/suite_test.go) in this repository.
### Expect vs Contains
Mimic works by wrapping both [Netflix/go-expect](https://github.com/Netflix/go-expect) and [hinshun/vt10x](https://github.com/hinshun/vt10x) to provide two APIs which require a brief explanation.
#### Expect
The expect-like APIs (`mimic.ExpectString` and `mimic.ExpectPattern`) watch the stream of stdout for a string or pattern, evaluating your criteria on each new byte written to the stream. Once criteria is met, you can't re-evaluate those same bytes. If these are concerns, consider using `mimic.ContainsString` and `mimic.ContainsPattern` which both flush pending writes (up to the flush timeout) prior to evaluating conditions.
For example, suppose you want to orchestrate the following terminal output:
```
? What is your name? Jim
? What is your github username? jimschubert
```
The following will work in your test:
```go
assert.NoError(t, mimic.ExpectString("What is your name?"))
```
Expecting on multiple conditions for the same line will fail because it's expecting the contents to be written to stdout again. For example, expecting a partial and full line as follows does not work:
```go
assert.NoError(t, mimic.ExpectString("What is your name?"))
mimic.WriteString("Jim")
// can't do this with ExpectString
assert.NoError(t, mimic.ExpectString("? What is your name?"))
```
You could use a pattern for both of these:
```go
assert.NoError(t, mimic.ExpectPattern("What is your.*name"))
mimic.WriteString("Jim")
assert.NoError(t, mimic.ExpectPattern("What is your.*name"))
mimic.WriteString("jimschubert")
```
The above example is contrived to demonstrate a concern with test performance when using ExpectPattern. **The pattern is evaluated against every new byte on the stream.** You could test this locally by adding a log message to RegexpMatcher.Match in this repository. You'd see something like this:
```
mimic: [RegexpMatcher] evaluating: ?
mimic: [RegexpMatcher] evaluating: ?
mimic: [RegexpMatcher] evaluating: ? W
mimic: [RegexpMatcher] evaluating: ? Wh
mimic: [RegexpMatcher] evaluating: ? Wha
mimic: [RegexpMatcher] evaluating: ? What
mimic: [RegexpMatcher] evaluating: ? What
mimic: [RegexpMatcher] evaluating: ? What i
mimic: [RegexpMatcher] evaluating: ? What is
mimic: [RegexpMatcher] evaluating: ? What is
mimic: [RegexpMatcher] evaluating: ? What is y
mimic: [RegexpMatcher] evaluating: ? What is yo
mimic: [RegexpMatcher] evaluating: ? What is you
mimic: [RegexpMatcher] evaluating: ? What is your
mimic: [RegexpMatcher] evaluating: ? What is your
mimic: [RegexpMatcher] evaluating: ? What is your n
mimic: [RegexpMatcher] evaluating: ? What is your na
mimic: [RegexpMatcher] evaluating: ? What is your nam
mimic: [RegexpMatcher] evaluating: ? What is your name
mimic: [RegexpMatcher] evaluating: ?
```
If you wanted to validate that `What is your name?` is not output twice, expecting via `assert.Error` will run until the timeout period. Suppose you have a timeout of 5 seconds (constructed with `mimic.WithIdleTimeout(5 * time.Second)`). The following `assert.Error` is technically valid, but adds 5 seconds to your test function:
```go
assert.NoError(t, mimic.ExpectString("What is your name?"))
mimic.WriteString("Jim")
assert.Error(t, mimic.ExpectString("What is your name?"), "This condition should succeed after 5 seconds!")
```
Once you're done with all interactions, it's a best practice to invoke `mimic.NoMoreExpectations()`. This flushes remaining bytes to stdout and expects `io.EOF` on the stream.
## Contains
The contains APIs (`mimic.ContainsString` and `mimic.ContainsPattern`) both flush pending writes (up to the flush timeout) prior to evaluating conditions.
For example, suppose you want to orchestrate the following terminal output:
```
? What is your name? Jim
? What is your github username? jimschubert
```
The following will work in your test:
```go
assert.True(t, mimic.ContainsString("What is your name?"))
```
The test case described in the [Expect](#expect) second which failed would work using ContainsString:
```go
assert.True(t, mimic.ContainsString("What is your name?"))
mimic.WriteString("Jim")
// can do this with ContainsString, but not ExpectString
assert.True(t, mimic.ContainsString("? What is your name?"))
```
`ContainsString` and `ContainsPattern` work on the full terminal view. Keep this in mind as the following which works serially in the Expect API works a little differently:
```go
assert.True(t, mimic.ContainsPattern("What is your.*name"))
mimic.WriteString("Jim")
// WARNING: Sill passes for "What is your name?" even if "What is your github username?" is never displayed
assert.True(t, mimic.ContainsPattern("What is your.*name"))
mimic.WriteString("jimschubert")
```
Since `ContainsPattern` works on the entire view contents, each regex passed to the function is evaluated once. If you were to add a logger within the `ContainsPattern` function, you'd see a trace similar to (one log for each of the above assertions):
```
mimic: [ContainsPattern] evaluating: What is your.*name
mimic: [ContainsPattern] evaluating: What is your.*name
```
You can always mix and match these APIs:
```go
assert.True(t, mimic.ContainsPattern("What is your.*name"))
mimic.WriteString("Jim")
assert.NoError(t, mimic.ExpectPattern("What is your.*name"))
mimic.WriteString("jimschubert")
```
**Prefer `ContainsString` or `ExpectString` over pattern based functions where possible.
## License
This project is [licensed](./LICENSE) under Apache 2.0.