PageSourceSearch

https://tunit.dev/assets/js/7a097c20.9b68c232.js

js tunit.dev collected 2026-10-02 07:51:59 UTC 7,226 bytes, 1 lines download raw bytes

1"use strict";(globalThis.webpackChunktunit_docs_site||=[]).push([[3154],{63925(e,s,t){t.r(s),t.d(s,{assets:()=>l,contentTitle:()=>a,default:()=>h,frontMatter:()=>o,metadata:()=>n,toc:()=>c});const n=JSON.parse('{"id":"guides/philosophy","title":"Philosophy","description":"TUnit does some things differently from other .NET testing frameworks. This page explains the thinking behind those choices.","source":"@site/docs/guides/philosophy.md","sourceDirName":"guides","slug":"/guides/philosophy","permalink":"/docs/guides/philosophy","draft":false,"unlisted":false,"tags":[],"version":"current","frontMatter":{},"sidebar":"docs","previous":{"title":"Troubleshooting & FAQ","permalink":"/docs/troubleshooting"}}');var i=t(74848),r=t(28453);const o={},a="Philosophy",l={},c=[{value:"Parallel by default",id:"parallel-by-default",level:2},{value:"New instance per test",id:"new-instance-per-test",level:2},{value:"Async everywhere",id:"async-everywhere",level:2},{value:"Source-generated test discovery",id:"source-generated-test-discovery",level:2},{value:"Built on Microsoft.Testing.Platform",id:"built-on-microsofttestingplatform",level:2},{value:"Type-safe assertions",id:"type-safe-assertions",level:2},{value:"Minimal boilerplate",id:"minimal-boilerplate",level:2},{value:"Comparison with other frameworks",id:"comparison-with-other-frameworks",level:2}];function d(e){const s={a:"a",code:"code",h1:"h1",h2:"h2",header:"header",p:"p",pre:"pre",...(0,r.R)(),...e.components};return(0,i.jsxs)(i.Fragment,{children:[(0,i.jsx)(s.header,{children:(0,i.jsx)(s.h1,{id:"philosophy",children:"Philosophy"})}),"\n",(0,i.jsx)(s.p,{children:"TUnit does some things differently from other .NET testing frameworks. This page explains the thinking behind those choices."}),"\n",(0,i.jsx)(s.h2,{id:"parallel-by-default",children:"Parallel by default"}),"\n",(0,i.jsx)(s.p,{children:"Most frameworks make you opt into parallelism. TUnit flips that \u2014 tests run in parallel from the start, because that's how you get fast feedback from large test suites."}),"\n",(0,i.jsx)(s.p,{children:"This also nudges you toward better test design. If your tests can't run in parallel, they're probably sharing state they shouldn't be. When they genuinely do need exclusive access to something (a shared file, a database, a hardware device), you opt out explicitly:"}),"\n",(0,i.jsx)(s.pre,{children:(0,i.jsx)(s.code,{className:"language-csharp",children:"[Test, NotInParallel]\npublic async Task ModifiesSharedConfigFile() { ... }\n"})}),"\n",(0,i.jsx)(s.h2,{id:"new-instance-per-test",children:"New instance per test"}),"\n",(0,i.jsx)(s.p,{children:"Every test method runs against a fresh instance of its class. Instance fields can't leak between tests, and you'll never see the \"Test B fails when Test A runs first\" mystery."}),"\n",(0,i.jsxs)(s.p,{children:["If you need shared state, use ",(0,i.jsx)(s.code,{children:"static"}),". That makes the sharing visible to anyone reading the code \u2014 no surprises."]}),"\n",(0,i.jsx)(s.h2,{id:"async-everywhere",children:"Async everywhere"}),"\n",(0,i.jsxs)(s.p,{children:["All assertions return ",(0,i.jsx)(s.code,{children:"Task"})," and must be awaited. This is probably TUnit's most controversial decision."]}),"\n",(0,i.jsx)(s.pre,{children:(0,i.jsx)(s.code,{className:"language-csharp",children:"await Assert.That(result).IsEqualTo(expected);\n"})}),"\n",(0,i.jsxs)(s.p,{children:["The reasoning: if everything is async, you never have to remember which operations need ",(0,i.jsx)(s.code,{children:"await"})," and which don't. Custom assertions can do genuinely async work \u2014 database queries, HTTP calls \u2014 without awkward sync-over-async hacks. And you avoid the deadlock problems that come with mixing sync and async code."]}),"\n",(0,i.jsx)(s.h2,{id:"source-generated-test-discovery",children:"Source-generated test discovery"}),"\n",(0,i.jsx)(s.p,{children:"TUnit uses Roslyn source generators to discover tests at compile time rather than runtime reflection. This makes test discovery fast, gives you compile-time errors for configuration mistakes instead of runtime surprises, and is what makes Native AOT and single-file publishing work."}),"\n",(0,i.jsxs)(s.p,{children:["If you need runtime flexibility, reflection mode is available with ",(0,i.jsx)(s.code,{children:"--reflection"}),"."]}),"\n",(0,i.jsx)(s.h2,{id:"built-on-microsofttestingplatform",children:"Built on Microsoft.Testing.Platform"}),"\n",(0,i.jsx)(s.p,{children:"TUnit uses Microsoft's modern testing platform rather than the legacy VSTest infrastru
1cture. It's faster, more extensible, and where Microsoft is investing going forward."}),"\n",(0,i.jsxs)(s.p,{children:["The trade-off is that some older tools only work with VSTest \u2014 Coverlet being the most notable. But ",(0,i.jsx)(s.code,{children:"Microsoft.Testing.Extensions.CodeCoverage"})," is the modern replacement and is included in the TUnit package automatically."]}),"\n",(0,i.jsx)(s.h2,{id:"type-safe-assertions",children:"Type-safe assertions"}),"\n",(0,i.jsx)(s.p,{children:"TUnit's assertions are extension methods on specific types, not generic methods that accept anything. Intellisense only shows assertions that make sense for what you're testing. You can't accidentally check if a string is negative, because that method doesn't exist on strings."}),"\n",(0,i.jsx)(s.pre,{children:(0,i.jsx)(s.code,{className:"language-csharp",children:'await Assert.That(user.Email)\n    .IsNotNull()\n    .And.Contains("@example.com");\n'})}),"\n",(0,i.jsx)(s.h2,{id:"minimal-boilerplate",children:"Minimal boilerplate"}),"\n",(0,i.jsxs)(s.p,{children:["Just put ",(0,i.jsx)(s.code,{children:"[Test]"})," on a method. No ",(0,i.jsx)(s.code,{children:"[TestClass]"}),", no ",(0,i.jsx)(s.code,{children:"[TestFixture]"}),", no base classes required. Data attributes like ",(0,i.jsx)(s.code,{children:"[Arguments]"})," and ",(0,i.jsx)(s.code,{children:"[MethodDataSource]"})," work on both classes and methods. Global usings are configured automatically."]}),"\n",(0,i.jsx)(s.p,{children:"The goal is to spend your time writing tests, not scaffolding."}),"\n",(0,i.jsx)(s.h2,{id:"comparison-with-other-frameworks",children:"Comparison with other frameworks"}),"\n",(0,i.jsxs)(s.p,{children:["For specific comparisons with xUnit, NUnit, and MSTest, see ",(0,i.jsx)(s.a,{href:"/docs/comparison/framework-differences",children:"Framework Differences"}),"."]}),"\n",(0,i.jsxs)(s.p,{children:["For migration guides, see ",(0,i.jsx)(s.a,{href:"/docs/migration/xunit",children:"xUnit"}),", ",(0,i.jsx)(s.a,{href:"/docs/migration/nunit",children:"NUnit"}),", or ",(0,i.jsx)(s.a,{href:"/docs/migration/mstest",children:"MSTest"}),"."]})]})}function h(e={}){const{wrapper:s}={...(0,r.R)(),...e.components};return s?(0,i.jsx)(s,{...e,children:(0,i.jsx)(d,{...e})}):d(e)}},28453(e,s,t){t.d(s,{R:()=>o,x:()=>a});var n=t(96540);const i={},r=n.createContext(i);function o(e){const s=n.useContext(r);return n.useMemo(function(){return"function"==typeof e?e(s):{...s,...e}},[s,e])}function a(e){let s;return s=e.disableParentContext?"function"==typeof e.components?e.components(i):e.components||i:o(e.components),n.createElement(r.Provider,{value:s},e.children)}}}]);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.