Test Tools · Unit & Integration Testing

NUnit & xUnit.net

The leading .NET unit testing frameworks. NUnit for classic style, xUnit.net for modern idiomatic .NET testing.

Overview

NUnit and xUnit.net are the two most popular unit testing frameworks for .NET. NUnit, inspired by JUnit, has been the standard for .NET testing since 2002. xUnit.net, created by the original NUnit developers in 2007, takes a more modern approach with cleaner architecture, better parallel execution, and fewer attributes.

Both frameworks integrate with Visual Studio, Rider, and all .NET CI/CD tools. Most NZ .NET teams use one or both. xUnit.net is the default choice for ASP.NET Core projects, while NUnit remains popular in legacy .NET Framework codebases.

What it's used for

NUnit/xUnit.net are essential for:

  • .NET unit testing: The standard frameworks for testing C# and VB.NET code.
  • ASP.NET Core testing: xUnit.net is the default for testing controllers, services, and middleware.
  • Integration testing: Test database access, API clients, and external services.
  • TDD workflows: Both frameworks support test-driven development with fast feedback.

Pros & Cons

Pros

  • Native .NET integration — works seamlessly with Visual Studio and Rider
  • Rich assertion libraries with fluent APIs
  • Extensive parameterisation and data-driven test support
  • Strong community and extensive documentation
  • Integrate with all .NET CI/CD tools

Cons

  • .NET only — not suitable for other languages
  • NUnit can feel dated compared to xUnit.net
  • xUnit.net has a steeper learning curve for NUnit migrants
  • Some advanced features require additional packages
  • Parallel test execution requires careful resource management

Platforms & Integrations

NUnit and xUnit.net run on Windows, macOS, and Linux via .NET Core/.NET 5+. They integrate with Visual Studio, Rider, VS Code, and all CI platforms.

Windows macOS Linux .NET C# F# VB.NET Visual Studio Rider VS Code ASP.NET Core GitHub Actions GitLab CI Jenkins Azure DevOps TeamCity

Pricing

TierCostIncludes
Open SourceFreeFull framework, all features, community support

NZ Context

NUnit and xUnit.net are standard in NZ .NET development teams. .NET has deep roots across NZ government agencies, banks, and large enterprise software shops — many of which have been running C# in production for over a decade. That means testers in these environments encounter NUnit suites in legacy codebases and xUnit.net in modernisation programmes, often within the same organisation. For NZ testers working in .NET environments, fluency in both frameworks is a practical career requirement, not just a nice-to-have.

Alternatives

  • MSTest — Microsoft's built-in testing framework. Simpler but less flexible.
  • Shouldly / FluentAssertions — Assertion libraries that pair with NUnit/xUnit for readable assertions.
  • SpecFlow — BDD framework for .NET using Gherkin syntax.

When to choose NUnit / xUnit

A quick decision guide for NZ teams evaluating .NET unit testing options.

Choose NUnit / xUnit when… Choose something else when… Combine with…
Your codebase is .NET — C#, F#, or VB.NET, any version from .NET Framework 4.x to .NET 8+ Your back-end is Node.js or Python — Jest, Vitest, or pytest will integrate far more naturally and avoid cross-language friction FluentAssertions for readable assertion messages that make failures self-explanatory in CI logs
You are starting a new ASP.NET Core project — xUnit.net is the Microsoft-endorsed default and ships with dotnet new templates Your team writes Gherkin-style acceptance criteria shared with non-technical stakeholders — SpecFlow sits on top of NUnit/xUnit but owns the test-definition layer; lead with SpecFlow instead Moq or NSubstitute for mocking dependencies; xUnit's fixture model and Moq's verify API were designed to work together
You inherit a legacy .NET Framework codebase already using NUnit — the migration cost to xUnit rarely justifies itself; polish the existing suite first You need end-to-end browser automation — Playwright for .NET or Selenium run inside xUnit tests, but the test layer is really Playwright/Selenium; don't conflate unit and UI testing tools coverlet + ReportGenerator for line-coverage reporting; integrates directly with dotnet test and Azure DevOps
You want fast parallel test execution — xUnit.net runs tests in parallel by default at the collection level; NUnit requires explicit [Parallelizable] attributes MSTest is already standardised across your organisation and switching frameworks would fracture shared test utilities and reporting pipelines — consistency beats marginal technical advantages Bogus for generating realistic NZ test data (names, addresses, Revenue NZ numbers) without hand-crafting fixtures

What I would do

Practitioner judgment on tool adoption, team onboarding, and when to swap.

If…

I were joining a TechServNZ delivery team running a large C# microservices estate on Azure — where NUnit suites already exist in most services but new work is greenfield

I would…

Standardise new services on xUnit.net and leave legacy NUnit suites alone. Write a short internal ADR (Architecture Decision Record) documenting the split so every engineer knows the rule: touch an old service, stay in NUnit; create a new service, start in xUnit. The consistency within each service matters more than uniformity across all services. I would also mandate FluentAssertions across both frameworks immediately — it pays off within a week when reading Azure DevOps pipeline failure logs.

If…

I were a QA engineer at Revenue NZ, working on the modernisation programme where .NET 6 APIs are being extracted from a decades-old COBOL core — test coverage is thin and the business risk of regression is very high

I would…

Use xUnit.net with NSubstitute for mocking, and invest the first sprint building out characterisation tests — tests that capture the current (possibly wrong) behaviour so regressions are visible immediately. I would not try to write "correct" tests first; I would write "what does it actually do right now" tests. This gives the team a safety net before any refactoring. Once coverage reaches a baseline I'm comfortable with, I would layer in proper assertions and then refactor with confidence. Revenue NZ's context means a silent regression affecting tax calculations is a serious public-trust failure, so speed of feedback matters more than test elegance.

If…

I were the sole QA at a CloudBooks add-on startup in Auckland — small team, all C#, everyone is also a developer, and there is no dedicated QA tooling budget or process yet

I would…

Pick xUnit.net from day one, add coverlet for coverage, and set up a GitHub Actions workflow that fails the build under 70% line coverage on any changed file. That single gate does more for quality than any manual process I could introduce. I would resist the temptation to add SpecFlow at this stage — with a team this small, Gherkin scenarios become orphaned documentation within a month. Plain xUnit tests written with descriptive method names (using the Should_[expected]_When_[condition] convention) are sufficient and require no tooling overhead to maintain.

The bottom line: NUnit and xUnit.net are both excellent — the choice rarely matters as much as the discipline around what gets tested. A poorly organised xUnit suite with no mocking strategy is worse than a well-maintained NUnit suite from 2012. Pick xUnit for new work, keep NUnit where it already runs, and spend your energy on test quality and coverage gates rather than framework debates.

Interview questions

Questions you are likely to get if you list NUnit / xUnit.net on your CV — with what interviewers are really testing for.

What is the difference between NUnit and xUnit.net, and which would you recommend for a new ASP.NET Core project?

What they’re really testing: Whether you understand framework philosophy versus just knowing syntax, and if you track the .NET ecosystem rather than repeating old habits.

Strong answer covers: xUnit.net’s opinionated, modern design (no [SetUp]/[TearDown], constructor injection for setup); NUnit’s JUnit heritage and richer attribute set; the fact that xUnit.net ships with dotnet new templates and is Microsoft’s endorsed default for ASP.NET Core; and an acknowledgement that NUnit is the pragmatic choice when you inherit an existing codebase that already uses it.

When would you use [Theory] / [InlineData] in xUnit over a plain [Fact], and what are the limitations?

What they’re really testing: Hands-on fluency with parameterised tests and awareness that data-driven tests are a tool, not a default.

Strong answer covers: Using [Theory] + [InlineData] / [MemberData] / [ClassData] to avoid copy-paste test duplication across boundary values (e.g. tax-rate thresholds); the limitation that [InlineData] only accepts compile-time constants; and NUnit’s equivalent [TestCase] / [TestCaseSource] for teams still on NUnit. Bonus: mention that each data row runs as a separate test entry in Azure DevOps or GitHub Actions reports.

You’re joining CloudBooks’s platform team in Auckland. Their C# microservices have 40% unit test coverage and a growing number of flaky tests in CI. Where do you start?

What they’re really testing: Practical prioritisation under real constraints — they want a QA engineer, not a theory lecturer.

Strong answer covers: Triage flaky tests first (flakiness destroys CI trust faster than low coverage); distinguish timing-dependent tests from true non-determinism; use xUnit’s [Collection] isolation to stop shared-state bleed between parallel tests; then raise coverage by targeting the riskiest business logic (billing, tax, integrations) rather than chasing a blanket percentage. Mention using coverlet + ReportGenerator to surface coverage gaps in PR reviews.

Your xUnit tests pass locally on Windows but fail intermittently on the Linux GitHub Actions runner. What do you investigate first?

What they’re really testing: Cross-platform debugging instinct and whether you understand CI environment differences — a common real-world pain point on NZ teams moving to cloud-native pipelines.

Strong answer covers: File path separators (\ vs /) causing File.ReadAllText failures; culture-sensitive string or date comparisons that differ between Windows en-NZ and Linux en-US defaults; tests sharing static state that runs sequentially locally but collides under xUnit’s default parallel execution on CI; and environment variables or secrets missing from the runner. Fix order: reproduce on a local Linux container first, then isolate the failing test class.

How would you structure a test project for an ASP.NET Core API with dozens of controllers, shared database access, and a need to run integration tests in isolation from unit tests?

What they’re really testing: Architectural thinking about test organisation — they want to see you can scale a test suite, not just write individual tests.

Strong answer covers: Separate test projects for unit vs integration tests (MyApi.Tests.Unit / MyApi.Tests.Integration) so CI can run units on every commit and integration tests on merge; use xUnit’s IClassFixture<WebApplicationFactory<T>> for integration tests that spin up the real ASP.NET Core host in memory; use [Collection] to share a single database fixture across related integration tests without rebuilding the schema per class; and add a Category trait so dotnet test --filter Category=Unit can exclude slow tests locally.

Learn more