fakeproto

package module
v0.0.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 17, 2026 License: MIT Imports: 10 Imported by: 0

README

fakeproto

Go Reference

Fill protobuf messages with random fake data using gofakeit as the generation engine.

gofakeit.Struct vs fakeproto.Message

gofakeit.Struct works great for plain Go structs annotated with fake tags, but falls short on protobuf-generated structs.

gofakeit.Struct fakeproto.Message
user := &pb.User{}
err := gofakeit.Struct(user)
if err != nil {
    // TODO: Handle error.
}

fmt.Println(protojson.Format(user))
{
 "id": "CrbhJHy",
❌// oneof field is skipped...
 "name": "dDheSag",
❌"status": 12374249, // out of enum
❌"createdAt": "283539-01-05T10:32:30Z",
 "age": 1308958553,
 "tags": [
  "lmVwo",
  "UFBN",
  "CIiUYBUi"
 ]
}

// email/phone are absent: gofakeit.Struct
// sets Go struct fields directly,
// bypassing proto reflection - oneof
// fields are invisible to it.
user := &pb.User{}
err := fakeproto.Message(user)
if err != nil {
    // TODO: Handle error.
}

fmt.Println(protojson.Format(user))
{
 "id": "z3f7cb4zc8kn4ukbk56x",
✅"email": "clifford@clark.com",
 "name": "Aleen Schroeder",
✅"status": "STATUS_ACTIVE",
✅"createdAt": "2006-07-23T05:03:41Z",
 "age": 29,
 "tags": [
  "not",
  "which",
  "example"
 ]
}

// email is set, phone is absent: login
// is a oneof - exactly one variant is
// chosen at random.
//

fakeproto walks the message descriptor via protoreflect - the same API used by the protobuf runtime itself - so it correctly handles protobuf features (see supported features), using gofakeit as the random-data engine under the hood.

Why a separate module?

gofakeit explicitly advertises zero dependencies - it does not pull in protobuf, reflection extensions, or generated code. Adding protobuf support to gofakeit itself would break that guarantee for every user who doesn't need it.

fakeproto is an opt-in companion module:

Users of gofakeit who don't need protobuf pay nothing. Users who do need it get a drop-in solution that delegates all data generation back to gofakeit.

Protobuf support lives in fakeproto, not in gofakeit, to preserve gofakeit's zero-dependency guarantee.

Installation

go get github.com/psyhatter/fakeproto@latest

Requires Go 1.23+.

Quick start

import (
    "github.com/brianvoe/gofakeit/v7"
    "github.com/psyhatter/fakeproto"
)

// Fill a message (non-deterministic).
user := &pb.User{}
err := fakeproto.Message(user)
if err != nil {
	// TODO: Handle error.
}

// Fill a message with a seeded faker for deterministic output (tests, snapshots).
user2 := &pb.User{}
err = fakeproto.Message(user2, fakeproto.WithFaker(gofakeit.New(42)))
if err != nil {
	// TODO: Handle error.
}

// Fill a standalone enum variable with a random declared value.
var s pb.Status
fakeproto.Enum(&s)

Supported features

  • All 15 proto3 scalar types
  • Nested messages (recursive, depth-limited)
  • repeated fields and map fields
  • enum - value chosen from the descriptor's declared set (including negative values); also available as standalone Enum[E](&e)
  • oneof - one variant selected at random
  • Proto3 optional scalars (explicit presence)
  • Well-known types: Timestamp, Duration, StringValue/Int32Value/… wrappers, Struct, ListValue, FieldMask
  • google.protobuf.Any - filled with a random structpb.Value
  • Field name heuristics: *email*, *name*, *city*, *zip*, *phone*, *url*, *uuid*, *currency*, …

Behavior

Field name heuristics

For string and numeric fields, fakeproto looks up the field's text name in gofakeit's generator registry before falling back to a random value. Fields named email, city, zip, latitude, year, and hundreds of others automatically receive semantically correct values without any configuration.

oneof

One variant is chosen at random per oneof. All other variants are left unset. Use WithSkipOneof to leave every oneof completely unset instead.

enum

Reserved numbers and names are never produced - only values declared in the .proto file can appear.

repeated fields and maps

The number of elements is chosen uniformly at random in [1, maxRepeated] (default max is 5). Map keys are guaranteed to be unique because they are generated independently and inserted into the map one by one - later entries silently overwrite earlier ones on collision, so the actual count may be slightly less than the chosen length for types with small key spaces (e.g. bool keys).

proto3 optional scalars

A field declared optional in proto3 has explicit presence. fakeproto leaves it unset with probability optionalProbability (default 10%). Set WithOptionalProbability(0) to always fill every optional field.

Depth limiting

fakeproto counts nesting depth starting at 1 for the root message. When a message-type field would be filled at depth > maxDepth, it is left nil instead. This prevents infinite recursion in self-referential schemas.

Determinism

Pass a seeded *gofakeit.Faker via WithFaker(gofakeit.New(seed)) to get fully reproducible output. The default gofakeit.GlobalFaker is shared and non-deterministic.

Options

Option Default Description
WithMaxDepth(n int) 5 Stop recursing into message fields at depth n.
WithMaxRepeated(n int) 5 Generate at most n elements in repeated fields and maps.
WithOptionalProbability(p float64) 0.10 Probability that an explicit proto3 optional scalar is left unset. 0 = always set, 1 = always unset.
WithSkipOneof() - Leave all oneof fields unset.

Limitations

No per-field generation control

gofakeit.Struct lets you override the generator for any field via a struct tag:

type User struct {
    Name  string `fake:"{firstname} {lastname}"`
    Phone string `fake:"(###) ###-####"`
}

fakeproto has no equivalent. Generation is driven by field names: fakeproto checks whether the field name matches any generator registered in gofakeit (email, name, city, phone, url, and hundreds of others) and calls it if found. When no match exists, a random value is used as a fallback. There is no way to attach a specific template to a field whose name does not match any registered generator.

This limitation may be addressed in a future version. One approach under consideration is a custom proto field option (fake.proto extension) that mirrors the struct-tag syntax. See docs/fake-field-option.md for the design sketch and the open problems that need to be solved first.

Acknowledgements

fakeproto would not exist without gofakeit by @brianvoe. gofakeit is one of the most thoughtfully designed fake-data libraries in the Go ecosystem - with zero dependencies, a clean API, and an impressive breadth of generators. fakeproto uses it as its sole data-generation engine. Thank you, Brian, and everyone who has contributed to gofakeit.

Relationship to gofakeit

fakeproto builds directly on top of gofakeit's data generators and is designed to feel like a natural extension of it. Once the API and feature coverage here stabilize, we intend to propose upstreaming this protobuf support to github.com/brianvoe/gofakeit - either as an optional submodule or as a referenced companion library - while preserving gofakeit's zero-dependency guarantee for users who don't need protobuf.

Documentation

Overview

Package fakeproto fills protobuf messages with random fake data.

It uses github.com/brianvoe/gofakeit/v7 as the data-generation engine and walks the protobuf message descriptor via protoreflect, which means it correctly handles oneof, map, repeated, proto3 optional presence, enum values from the declared set, and well-known types.

Why a separate module?

Protobuf support lives here, not in gofakeit, to preserve gofakeit's zero-dependency guarantee. Users who don't need protobuf don't pay for it.

Basic usage

var msg mypb.MyMessage
if err := fakeproto.Message(&msg); err != nil {
    log.Fatal(err)
}

Use WithFaker to supply a seeded gofakeit.Faker for deterministic output:

f := gofakeit.New(42) // fixed seed
var msg mypb.MyMessage
if err := fakeproto.Message(&msg, fakeproto.WithFaker(f)); err != nil {
    log.Fatal(err)
}

Use Enum to fill a single enum variable with a random declared value:

var s mypb.Status
fakeproto.Enum(&s)

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func Enum

func Enum[E ProtoEnum](e *E, opts ...Option)

Enum sets *e to a random value chosen from the enum's declared set. Reserved numbers are never produced.

var p pb.Priority
fakeproto.Enum(&p)
Example
package main

import (
	"fmt"

	"github.com/brianvoe/gofakeit/v7"

	"github.com/psyhatter/fakeproto"
	testpb "github.com/psyhatter/fakeproto/internal/test/gen"
)

func main() {
	var p testpb.Priority
	fakeproto.Enum(&p, fakeproto.WithFaker(gofakeit.New(42)))
	fmt.Println(p)
}
Output:
PRIORITY_MEDIUM

func Message

func Message(msg proto.Message, opts ...Option) error

Message fills msg with random fake data.

Use WithFaker to supply a seeded gofakeit.Faker for deterministic output:

f := gofakeit.New(42)
err := fakeproto.Message(msg, fakeproto.WithFaker(f))
Example
package main

import (
	"bytes"
	"encoding/json"
	"fmt"

	"github.com/brianvoe/gofakeit/v7"
	"google.golang.org/protobuf/encoding/protojson"

	"github.com/psyhatter/fakeproto"
	testpb "github.com/psyhatter/fakeproto/internal/test/gen"
)

func main() {
	user := &testpb.User{}
	// WithFaker sets a fixed seed so the output is always identical.
	if err := fakeproto.Message(user, fakeproto.WithFaker(gofakeit.New(657))); err != nil {
		panic(err) // TODO: Handle error.
	}

	// protojson.Marshal works better with timestamppb.Timestamp than json.Marshal.
	b, err := protojson.Marshal(user)
	if err != nil {
		panic(err) // TODO: Handle error.
	}

	// To make the output prettier.
	buf := bytes.NewBuffer(nil)
	err = json.Indent(buf, b, "", " ")
	if err != nil {
		panic(err) // TODO: Handle error.
	}
	fmt.Println(buf.String())

}
Output:
{
 "id": "z3f7cb4zc8kn4ukbk56x",
 "email": "cliffordking@clark.com",
 "name": "Aleen Schroeder",
 "status": "STATUS_ACTIVE",
 "createdAt": "2006-07-23T05:03:41.200315279Z",
 "age": 29,
 "tags": [
  "not",
  "which",
  "example"
 ]
}

Types

type Option

type Option func(*config)

Option configures the behavior of Message and Enum.

func WithFaker

func WithFaker(f *gofakeit.Faker) Option

WithFaker sets the gofakeit.Faker instance used for random data generation. A seeded faker (gofakeit.New) produces deterministic output, which is useful for snapshot tests and godoc examples. Defaults to gofakeit.GlobalFaker.

func WithMaxDepth

func WithMaxDepth(d int) Option

WithMaxDepth sets the maximum JSON-container nesting depth of the generated output. Each message, array, and map object counts as one container level. Containers that would exceed d are left empty or nil. Default is 5.

func WithMaxRepeated

func WithMaxRepeated(n int) Option

WithMaxRepeated sets the maximum length of generated repeated fields and map entries. The actual length is chosen randomly in [1, n]. Default is 5.

func WithOptionalProbability

func WithOptionalProbability(p float64) Option

WithOptionalProbability sets the probability p in [0, 1] that an explicit proto3 optional scalar field is left unset. p=0 means always set; p=1 means always unset. Default is 0.10.

func WithSkipOneof

func WithSkipOneof() Option

WithSkipOneof instructs the generator to leave all oneof fields unset.

type ProtoEnum

type ProtoEnum interface {
	protoreflect.Enum
	~int32
}

ProtoEnum is satisfied by every protobuf-generated enum type. All such types have int32 as their underlying type.

Directories

Path Synopsis
internal

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL