diff --git a/.editorconfig b/.editorconfig index be9f7c581..4cab270c3 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,29 +1,109 @@ -; EditorConfig to support per-solution formatting. -; Use the EditorConfig VS add-in to make this work. -; http://editorconfig.org/ +# EditorConfig is awesome:http://EditorConfig.org -; This is the default for the codeline. +# top-most EditorConfig file root = true +# Don't use tabs for indentation. [*] -end_of_line = CRLF +indent_style = space +# (Please don't specify an indent_size here; that has too many unintended consequences.) -[*.{cs,txt,md}] -indent_style = tab +# Code files +[*.{cs,csx,vb,vbx}] indent_size = 4 -[*.{sln,proj,csproj,fsproj,vbproj,props,targets,xml,xdoc,config,nuspec}] -indent_style = tab +# Xml project files +[*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,projitems,shproj,msbuildproj,props,targets}] indent_size = 2 -[*.Designer.cs] -indent_style = space -indent_size = 4 +# Xml config files +[*.{ruleset,config,nuspec,resx,vsixmanifest,vsct}] +indent_size = 2 -[*.resx] -indent_style = space +# YAML files +[*.{yaml,yml}] indent_size = 2 -[appveyor.yml] -indent_style = space +# JSON files +[*.json] indent_size = 2 + +# Dotnet code style settings: +[*.{cs,vb}] +# Sort using and Import directives with System.* appearing first +dotnet_sort_system_directives_first = true +# Avoid "this." and "Me." if not necessary +dotnet_style_qualification_for_field = false:suggestion +dotnet_style_qualification_for_property = false:suggestion +dotnet_style_qualification_for_method = false:suggestion +dotnet_style_qualification_for_event = false:suggestion + +# Use language keywords instead of framework type names for type references +dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion +dotnet_style_predefined_type_for_member_access = true:suggestion + +# Suggest more modern language features when available +dotnet_style_object_initializer = true:suggestion +dotnet_style_collection_initializer = true:suggestion +dotnet_style_coalesce_expression = true:suggestion +dotnet_style_null_propagation = true:suggestion +dotnet_style_explicit_tuple_names = true:suggestion + +# CSharp code style settings: + +# IDE0040: Add accessibility modifiers +dotnet_style_require_accessibility_modifiers = omit_if_default:error + +# IDE0040: Add accessibility modifiers +dotnet_diagnostic.IDE0040.severity = error + +[*.cs] +# Top-level files are definitely OK +csharp_using_directive_placement = outside_namespace:silent +csharp_style_namespace_declarations = block_scoped:silent +csharp_prefer_simple_using_statement = true:suggestion +csharp_prefer_braces = true:silent + +# Prefer "var" everywhere +csharp_style_var_for_built_in_types = true:suggestion +csharp_style_var_when_type_is_apparent = true:suggestion +csharp_style_var_elsewhere = true:suggestion + +# Prefer method-like constructs to have an expression-body +csharp_style_expression_bodied_methods = true:none +csharp_style_expression_bodied_constructors = true:none +csharp_style_expression_bodied_operators = true:none + +# Prefer property-like constructs to have an expression-body +csharp_style_expression_bodied_properties = true:none +csharp_style_expression_bodied_indexers = true:none +csharp_style_expression_bodied_accessors = true:none + +# Suggest more modern language features when available +csharp_style_pattern_matching_over_is_with_cast_check = true:error +csharp_style_pattern_matching_over_as_with_null_check = true:error +csharp_style_inlined_variable_declaration = true:suggestion +csharp_style_throw_expression = true:suggestion +csharp_style_conditional_delegate_call = true:suggestion + +# Newline settings +csharp_new_line_before_open_brace = all +csharp_new_line_before_else = true +csharp_new_line_before_catch = true +csharp_new_line_before_finally = true +csharp_new_line_before_members_in_object_initializers = true +csharp_new_line_before_members_in_anonymous_types = true + +# Test settings +[**/*Tests*/**{.cs,.vb}] +# xUnit1013: Public method should be marked as test. Allows using records as test classes +dotnet_diagnostic.xUnit1013.severity = none + +# CS9113: Parameter is unread (usually, ITestOutputHelper) +dotnet_diagnostic.CS9113.severity = none + +# Default severity for analyzer diagnostics with category 'Style' +dotnet_analyzer_diagnostic.category-Style.severity = none + +# VSTHRD200: Use "Async" suffix for async methods +dotnet_diagnostic.VSTHRD200.severity = none diff --git a/.gitattributes b/.gitattributes index b4bc13442..7c375796c 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,2 +1,24 @@ -# Auto detect text files and perform LF normalization -* text=auto \ No newline at end of file +# sln, csproj files (and friends) are always CRLF, even on linux +*.sln text eol=crlf +*.proj text eol=crlf +*.csproj text eol=crlf + +# These are windows specific files which we may as well ensure are +# always crlf on checkout +*.bat text eol=crlf +*.cmd text eol=crlf + +# Opt in known filetypes to always normalize line endings on checkin +# and always use native endings on checkout +*.c text +*.config text +*.h text +*.cs text +*.md text +*.tt text +*.txt text + +# Some must always be checked out as lf so enforce that for those files +# If these are not lf then bash/cygwin on windows will not be able to +# excute the files +*.sh text eol=lf \ No newline at end of file diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 000000000..ec364b622 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,34 @@ +# Please see the documentation for all configuration options: +# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates + +version: 2 +updates: +- package-ecosystem: nuget + directory: / + schedule: + interval: daily + groups: + Azure: + patterns: + - "Azure*" + - "Microsoft.Azure*" + System: + patterns: + - "System*" + Extensions: + patterns: + - "Microsoft.Extensions*" + Web: + patterns: + - "Microsoft.AspNetCore*" + Tests: + patterns: + - "Microsoft.NET.Tests*" + - "xunit*" + - "coverlet*" + ThisAssembly: + patterns: + - "ThisAssembly*" + ProtoBuf: + patterns: + - "protobuf-*" diff --git a/.github/release.yml b/.github/release.yml new file mode 100644 index 000000000..9a018cd4f --- /dev/null +++ b/.github/release.yml @@ -0,0 +1,32 @@ +changelog: + exclude: + labels: + - bydesign + - dependencies + - duplicate + - question + - invalid + - wontfix + - need info + - docs + - techdebt + authors: + - devlooped-bot + - dependabot + - github-actions + categories: + - title: ✨ Implemented enhancements + labels: + - enhancement + - title: πŸ› Fixed bugs + labels: + - bug + - title: πŸ“ Documentation updates + labels: + - docs + - title: πŸ”¨ Other + labels: + - '*' + exclude: + labels: + - dependencies diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 000000000..0007ac7b2 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,67 @@ +ο»Ώ# Builds and runs tests in all three supported OSes +# Pushes CI feed if secrets.SLEET_CONNECTION is provided + +name: build +on: + workflow_dispatch: + push: + branches: [ main, dev, 'dev/*', 'feature/*', 'rel/*' ] + paths-ignore: + - changelog.md + - code-of-conduct.md + - security.md + - support.md + - readme.md + pull_request: + types: [opened, synchronize, reopened] + +env: + DOTNET_NOLOGO: true + VersionPrefix: 42.42.${{ github.run_number }} + VersionLabel: ${{ github.ref }} + +defaults: + run: + shell: bash + +jobs: + build: + runs-on: windows-latest + steps: + - name: 🀘 checkout + uses: actions/checkout@v2 + with: + submodules: recursive + fetch-depth: 0 + + - name: πŸ™ build + run: dotnet build -m:1 + + - name: πŸ§ͺ test + uses: ./.github/workflows/test + + - name: πŸ“¦ pack + run: dotnet pack -m:1 + + # Only push CI package to sleet feed if building on ubuntu (fastest) + - name: πŸš€ sleet + env: + SLEET_CONNECTION: ${{ secrets.SLEET_CONNECTION }} + if: env.SLEET_CONNECTION != '' + run: | + dotnet tool install -g --version 4.0.18 sleet + sleet push bin --config none -f --verbose -p "SLEET_FEED_CONTAINER=nuget" -p "SLEET_FEED_CONNECTIONSTRING=${{ secrets.SLEET_CONNECTION }}" -p "SLEET_FEED_TYPE=azure" || echo "No packages found" + + dotnet-format: + runs-on: windows-latest + steps: + - name: 🀘 checkout + uses: actions/checkout@v2 + with: + submodules: recursive + fetch-depth: 0 + + - name: βœ“ ensure format + run: | + dotnet format whitespace --verify-no-changes -v:diag --exclude ~/.nuget + dotnet format style --verify-no-changes -v:diag --exclude ~/.nuget diff --git a/.github/workflows/changelog.config b/.github/workflows/changelog.config new file mode 100644 index 000000000..cd34ee757 --- /dev/null +++ b/.github/workflows/changelog.config @@ -0,0 +1,9 @@ +usernames-as-github-logins=true +issues_wo_labels=true +pr_wo_labels=true +exclude-labels=bydesign,dependencies,duplicate,question,invalid,wontfix,need info,docs +enhancement-label=:sparkles: Implemented enhancements: +bugs-label=:bug: Fixed bugs: +issues-label=:hammer: Other: +pr-label=:twisted_rightwards_arrows: Merged: +unreleased=false diff --git a/.github/workflows/changelog.yml b/.github/workflows/changelog.yml new file mode 100644 index 000000000..b120b7360 --- /dev/null +++ b/.github/workflows/changelog.yml @@ -0,0 +1,39 @@ +ο»Ώname: changelog +on: + workflow_dispatch: + release: + types: [released] + +jobs: + changelog: + runs-on: ubuntu-latest + steps: + - name: πŸ€– defaults + uses: devlooped/actions-bot@v1 + with: + name: ${{ secrets.BOT_NAME }} + email: ${{ secrets.BOT_EMAIL }} + gh_token: ${{ secrets.GH_TOKEN }} + github_token: ${{ secrets.GITHUB_TOKEN }} + + - name: 🀘 checkout + uses: actions/checkout@v2 + with: + fetch-depth: 0 + ref: main + token: ${{ env.GH_TOKEN }} + + - name: βš™ ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: 3.0.3 + + - name: βš™ changelog + run: | + gem install github_changelog_generator + github_changelog_generator --user ${GITHUB_REPOSITORY%/*} --project ${GITHUB_REPOSITORY##*/} --token $GH_TOKEN --o changelog.md --config-file .github/workflows/changelog.config + + - name: πŸš€ changelog + run: | + git add changelog.md + (git commit -m "πŸ–‰ Update changelog with ${GITHUB_REF#refs/*/}" && git push) || echo "Done" \ No newline at end of file diff --git a/.github/workflows/combine-prs.yml b/.github/workflows/combine-prs.yml new file mode 100644 index 000000000..0255974f2 --- /dev/null +++ b/.github/workflows/combine-prs.yml @@ -0,0 +1,157 @@ +# Source: https://github.com/hrvey/combine-prs-workflow +# Tweaks: regex support for branch + +name: 'β›™ combine-prs' + +on: + workflow_dispatch: + inputs: + branchExpression: + description: 'Regular expression to match against PR branches to find combinable PRs' + required: true + default: 'dependabot' + mustBeGreen: + description: 'Only combine PRs that are green (status is success)' + required: true + default: true + combineTitle: + description: 'Title of the combined PR' + required: true + default: '⬆️ Bump dependencies' + combineBranchName: + description: 'Name of the branch to combine PRs into' + required: true + default: 'combine-prs' + ignoreLabel: + description: 'Exclude PRs with this label' + required: true + default: 'nocombine' + +jobs: + combine-prs: + name: ${{ github.event.inputs.combineBranchName }} + runs-on: ubuntu-latest + steps: + - uses: actions/github-script@v6 + with: + github-token: ${{secrets.GITHUB_TOKEN}} + script: | + const pulls = await github.paginate('GET /repos/:owner/:repo/pulls', { + owner: context.repo.owner, + repo: context.repo.repo + }); + const branchRegExp = new RegExp(`${{github.event.inputs.branchExpression}}`); + let branchesAndPRStrings = []; + let baseBranch = null; + let baseBranchSHA = null; + for (const pull of pulls) { + const branch = pull['head']['ref']; + console.log('Pull for branch: ' + branch); + if (branchRegExp.test(branch)) { + console.log('Branch matched: ' + branch); + let statusOK = true; + if(${{ github.event.inputs.mustBeGreen }}) { + console.log('Checking green status: ' + branch); + const stateQuery = `query($owner: String!, $repo: String!, $pull_number: Int!) { + repository(owner: $owner, name: $repo) { + pullRequest(number:$pull_number) { + commits(last: 1) { + nodes { + commit { + statusCheckRollup { + state + } + } + } + } + } + } + }` + const vars = { + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: pull['number'] + }; + const result = await github.graphql(stateQuery, vars); + const [{ commit }] = result.repository.pullRequest.commits.nodes; + const state = commit.statusCheckRollup.state + console.log('Validating status: ' + state); + if(state != 'SUCCESS') { + console.log('Discarding ' + branch + ' with status ' + state); + statusOK = false; + } + } + console.log('Checking labels: ' + branch); + const labels = pull['labels']; + for(const label of labels) { + const labelName = label['name']; + console.log('Checking label: ' + labelName); + if(labelName == '${{ github.event.inputs.ignoreLabel }}') { + console.log('Discarding ' + branch + ' with label ' + labelName); + statusOK = false; + } + } + if (statusOK) { + console.log('Adding branch to array: ' + branch); + const prString = '#' + pull['number'] + ' ' + pull['title']; + branchesAndPRStrings.push({ branch, prString }); + baseBranch = pull['base']['ref']; + baseBranchSHA = pull['base']['sha']; + } + } + } + if (branchesAndPRStrings.length == 0) { + core.setFailed('No PRs/branches matched criteria'); + return; + } + if (branchesAndPRStrings.length == 1) { + core.setFailed('Only one PR/branch matched criteria'); + return; + } + + try { + await github.rest.git.createRef({ + owner: context.repo.owner, + repo: context.repo.repo, + ref: 'refs/heads/' + '${{ github.event.inputs.combineBranchName }}', + sha: baseBranchSHA + }); + } catch (error) { + console.log(error); + core.setFailed('Failed to create combined branch - maybe a branch by that name already exists?'); + return; + } + + let combinedPRs = []; + let mergeFailedPRs = []; + for(const { branch, prString } of branchesAndPRStrings) { + try { + await github.rest.repos.merge({ + owner: context.repo.owner, + repo: context.repo.repo, + base: '${{ github.event.inputs.combineBranchName }}', + head: branch, + }); + console.log('Merged branch ' + branch); + combinedPRs.push(prString); + } catch (error) { + console.log('Failed to merge branch ' + branch); + mergeFailedPRs.push(prString); + } + } + + console.log('Creating combined PR'); + const combinedPRsString = combinedPRs.join('\n'); + let body = 'β›™ Combined PRs:\n' + combinedPRsString; + if(mergeFailedPRs.length > 0) { + const mergeFailedPRsString = mergeFailedPRs.join('\n'); + body += '\n\n⚠️ The following PRs were left out due to merge conflicts:\n' + mergeFailedPRsString + } + await github.rest.pulls.create({ + owner: context.repo.owner, + repo: context.repo.repo, + title: 'β›™ ${{github.event.inputs.combineTitle}}', + head: '${{ github.event.inputs.combineBranchName }}', + base: baseBranch, + body: body + }); diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 000000000..f3e8d0a94 --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,38 @@ +ο»Ώ# Builds a final release version and pushes to nuget.org +# whenever a release is published. +# Requires: secrets.NUGET_API_KEY + +name: publish +on: + release: + types: [released] + +env: + DOTNET_NOLOGO: true + Configuration: Release + +defaults: + run: + shell: bash + +jobs: + publish: + runs-on: windows-latest + steps: + - name: 🀘 checkout + uses: actions/checkout@v2 + with: + submodules: recursive + fetch-depth: 0 + + - name: πŸ™ build + run: dotnet build -m:1 -p:version=${GITHUB_REF#refs/*/v} + + - name: πŸ§ͺ test + uses: ./.github/workflows/test + + - name: πŸ“¦ pack + run: dotnet pack -m:1 -p:version=${GITHUB_REF#refs/*/v} + + - name: πŸš€ nuget + run: dotnet nuget push ./bin/**/*.nupkg -s https://api.nuget.org/v3/index.json -k ${{secrets.NUGET_API_KEY}} --skip-duplicate diff --git a/.github/workflows/test/action.yml b/.github/workflows/test/action.yml new file mode 100644 index 000000000..4a7dbae33 --- /dev/null +++ b/.github/workflows/test/action.yml @@ -0,0 +1,36 @@ +name: test +description: runs dotnet tests with retry +runs: + using: "composite" + steps: + - name: πŸ§ͺ test + shell: bash --noprofile --norc {0} + env: + LC_ALL: en_US.utf8 + run: | + [ -f .bash_profile ] && source .bash_profile + counter=0 + exitcode=0 + reset="\e[0m" + warn="\e[0;33m" + while [ $counter -lt 6 ] + do + # run test and forward output also to a file in addition to stdout (tee command) + if [ $filter ] + then + echo -e "${warn}Retry $counter for $filter ${reset}" + dotnet test --no-build -m:1 --blame-hang --blame-hang-timeout 5m --filter=$filter | tee ./output.log + else + dotnet test --no-build -m:1 --blame-hang --blame-hang-timeout 5m | tee ./output.log + fi + # capture dotnet test exit status, different from tee + exitcode=${PIPESTATUS[0]} + if [ $exitcode == 0 ] + then + exit 0 + fi + # cat output, get failed test names, remove trailing whitespace, sort+dedupe, join as FQN~TEST with |, remove trailing |. + filter=$(cat ./output.log | grep -o -P '(?<=\sFailed\s)[\w\._]*' | sed 's/ *$//g' | sort -u | awk 'BEGIN { ORS="|" } { print("FullyQualifiedName~" $0) }' | grep -o -P '.*(?=\|$)') + ((counter++)) + done + exit $exitcode diff --git a/.gitignore b/.gitignore index 0f7dd1ba4..0c18de793 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,35 @@ -out bin +app obj +artifacts +pack +TestResults +.vs +.vscode +.idea + *.suo +*.sdf +*.userprefs *.user +*.nupkg +*.metaproj +*.tmp +*.log *.cache -.vs -*/**/.* +*.binlog +*.zip +__azurite*.* +__*__ + +.nuget +*.lock.json +*.nuget.props +*.nuget.targets + +node_modules +_site +.jekyll-metadata +.jekyll-cache +Gemfile.lock +package-lock.json diff --git a/.netconfig b/.netconfig index c2bb4a84e..e5d80548a 100644 --- a/.netconfig +++ b/.netconfig @@ -18,3 +18,96 @@ sha = 8990ebb36199046e0b8098bad9e46dcef739c56e etag = e1dc114d2e8b57d50649989d32dbf0c9080ec77da3738a4cc79e9256d6ca5d3e weak +[file] + url = https://github.com/devlooped/oss +[file ".netconfig"] + url = https://github.com/devlooped/oss/blob/main/.netconfig + skip +[file "readme.md"] + url = https://github.com/devlooped/oss/blob/main/readme.md + skip +[file ".editorconfig"] + url = https://github.com/devlooped/oss/blob/main/.editorconfig + sha = c779d3d4e468358106dea03e93ba2cd35bb01ecb + etag = 7298c6450967975a8782b5c74f3071e1910fc59686e48f9c9d5cd7c68213cf59 + weak +[file ".gitattributes"] + url = https://github.com/devlooped/oss/blob/main/.gitattributes + sha = 0683ee777d7d878d4bf013d7deea352685135a05 + etag = 7acb32f5fa6d4ccd9c824605a7c2b8538497f0068c165567807d393dcf4d6bb7 + weak +[file ".github/dependabot.yml"] + url = https://github.com/devlooped/oss/blob/main/.github/dependabot.yml + sha = cba10bbf7bb08d841d2ddc990b67bc3f2c58fba0 + etag = fc84e2bb7a348609b75bb20145333306e2030083c6929edcc4f675907b8c52ad + weak +[file ".github/release.yml"] + url = https://github.com/devlooped/oss/blob/main/.github/release.yml + sha = 1afd173fe8f81b510c597737b0d271218e81fa73 + etag = 482dc2c892fc7ce0cb3a01eb5d9401bee50ddfb067d8cb85873555ce63cf5438 + weak +[file ".github/workflows/build.yml"] + url = https://github.com/devlooped/oss/blob/main/.github/workflows/build.yml + skip +[file ".github/workflows/changelog.config"] + url = https://github.com/devlooped/oss/blob/main/.github/workflows/changelog.config + sha = 055a8b7c94b74ae139cce919d60b83976d2a9942 + etag = ddb17acb5872e9e69a76f9dec0ca590f25382caa2ccf750df058dcabb674db2b + weak +[file ".github/workflows/changelog.yml"] + url = https://github.com/devlooped/oss/blob/main/.github/workflows/changelog.yml + sha = a4b66eb5f4dfb9704502f19f59ba33cb4855188c + etag = 54c0b571648b1055beb3ddac180b34e93a9869b9f0277de306901b2c1dbe0b2c + weak +[file ".github/workflows/combine-prs.yml"] + url = https://github.com/devlooped/oss/blob/main/.github/workflows/combine-prs.yml + sha = c1610886eba42cb250e3894aed40c0a258cd383d + etag = 598ee294649a44d4c5d5934416c01183597d08aa7db7938453fd2bbf52a4e64d + weak +[file ".github/workflows/publish.yml"] + url = https://github.com/devlooped/oss/blob/main/.github/workflows/publish.yml + skip +[file ".github/workflows/test/action.yml"] + url = https://github.com/devlooped/oss/blob/main/.github/workflows/test/action.yml + sha = 9a1b07589b9bde93bc12528e9325712a32dec418 + etag = b54216ac431a83ce5477828d391f02046527e7f6fffd21da1d03324d352c3efb + weak +[file ".gitignore"] + url = https://github.com/devlooped/oss/blob/main/.gitignore + sha = b87a8a795a4c2b6830602225c066c11108552a99 + etag = 96e0860052044780f1fc9e3bdfbee09d82d5dddb8b1217d67460fc7330a64dd8 + weak +[file "Directory.Build.rsp"] + url = https://github.com/devlooped/oss/blob/main/Directory.Build.rsp + sha = ae25fae9d7daf0cb47d537ba870914aa3052f0c9 + etag = 6a6c6e1d3895df953abf14c82b0899e3eea75cdcd679f6212dcfea15183d73d6 + weak +[file "_config.yml"] + url = https://github.com/devlooped/oss/blob/main/_config.yml + sha = fa83a5161ba52bc5d510ce0ba75ee0b1f8d4bc63 + etag = 9139148f845adf503fd3c3c140eb64421fc476a1f9c027fc50825c0efb05f557 + weak +[file "assets/css/style.scss"] + url = https://github.com/devlooped/oss/blob/main/assets/css/style.scss + sha = 9db26e2710b084d219d6355339d822f159bf5780 + etag = f710d8919abfd5a8d00050b74ba7d0bb05c6d02e40842a3012eb96555c208504 + weak +[file "license.txt"] + url = https://github.com/devlooped/oss/blob/main/license.txt + skip +[file "src/Directory.Build.props"] + url = https://github.com/devlooped/oss/blob/main/src/Directory.Build.props + sha = 6ae80a175a8f926ac5d9ffb0f6afd55d85cc9320 + etag = 69d4b16c14d5047b3ed812dbf556b0b8d77deb86f73af04b9bd3640220056fa8 + weak +[file "src/Directory.Build.targets"] + url = https://github.com/devlooped/oss/blob/main/src/Directory.Build.targets + sha = 1514d15399a7d545ad92a0e9d57dc8295fdd6af8 + etag = 428f80b0786ff17b836c7a5b0640948724855d17933e958642b22849ac00dadb + weak +[file "src/kzu.snk"] + url = https://github.com/devlooped/oss/blob/main/src/kzu.snk + skip +[file "src/nuget.config"] + url = https://github.com/devlooped/oss/blob/main/src/nuget.config + skip diff --git a/Before.Moq.sln.targets b/Before.Moq.sln.targets deleted file mode 100644 index ebca5240d..000000000 --- a/Before.Moq.sln.targets +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f48ff339a..c79d0f448 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -3,13 +3,6 @@ Moq welcomes your contributions! :heart: -## A quick word about the upcoming next major version of Moq (v5) - -The next major version of Moq is currently in active development (over at the [moq/moq](https://github.com/moq/moq) repository). Because of this, we are somewhat hesitant about big API changes in Moq v4. The plan for v5 is to have an API very similar to Moq v4 (to make migration easy). Therefore, any radical changes in v4 might have to be reproduced in v5, which costs additional time. - -For this reason, the main (but not sole!) focus at this repository (v4) is on bug fixing, improving stability, and performance. That being said, small additions stand a good chance of getting in if they seem generally useful. - - ## Asking usage questions This repository's focus is on the development of Moq (i.e. fixing bugs and improving features; see above). Because the Moq team is rather small, our resources to answer usage questions is very limited. You might be better off asking usage questions over at [Stack Overflow](https://stackoverflow.com) as your question will meet with a much larger audience. Please observe SO's rules and etiquette, and tag your question with `moq`. @@ -17,9 +10,9 @@ This repository's focus is on the development of Moq (i.e. fixing bugs and impro ## Submitting a bug report (as an issue) -If you think you've found a bug, please start by searching [the changelog](https://github.com/moq/moq4/blob/main/CHANGELOG.md) and [GitHub issues](https://github.com/moq/moq4/issues) (both open and closed ones!) to see if the problem has already been addressed or documented in any way. +If you think you've found a bug, please start by searching [the changelog](https://github.com/moq/moq/blob/main/CHANGELOG.md) and [GitHub issues](https://github.com/moq/moq/issues) (both open and closed ones!) to see if the problem has already been addressed or documented in any way. -If you find nothing of relevance, [open a new issue](https://github.com/moq/moq4/issues/new). +If you find nothing of relevance, [open a new issue](https://github.com/moq/moq/issues/new). **What to include:** Try to include the following information in it: diff --git a/Directory.Build.props b/Directory.Build.props deleted file mode 100644 index 304a15010..000000000 --- a/Directory.Build.props +++ /dev/null @@ -1,15 +0,0 @@ - - - - $(MSBuildThisFileDirectory) - $(RootDirectory)build\ - $(RootDirectory)out\ - Release - - - - - - - - diff --git a/Directory.Build.rsp b/Directory.Build.rsp new file mode 100644 index 000000000..7c0dbc1ec --- /dev/null +++ b/Directory.Build.rsp @@ -0,0 +1,5 @@ +# See https://docs.microsoft.com/en-us/visualstudio/msbuild/msbuild-response-files +-nr:false +-m:1 +-v:m +-clp:Summary;ForceNoAlign \ No newline at end of file diff --git a/GitInfo.txt b/GitInfo.txt deleted file mode 100644 index df66f9557..000000000 --- a/GitInfo.txt +++ /dev/null @@ -1 +0,0 @@ -4.18.4 diff --git a/Moq.sln b/Moq.sln index 3b630c3d8..5075d6c07 100644 --- a/Moq.sln +++ b/Moq.sln @@ -1,24 +1,25 @@ Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.27004.2002 +# Visual Studio Version 17 +VisualStudioVersion = 17.7.33913.275 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{A5A5B8C3-D43F-43C6-AEC7-6FA7C35B8BE9}" ProjectSection(SolutionItems) = preProject - appveyor.yml = appveyor.yml + .netconfig = .netconfig + .github\workflows\build.yml = .github\workflows\build.yml CHANGELOG.md = CHANGELOG.md CONTRIBUTING.md = CONTRIBUTING.md - GitInfo.txt = GitInfo.txt + src\Directory.props = src\Directory.props License.txt = License.txt README.md = README.md EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Moq", "src\Moq\Moq.csproj", "{1C91AC30-5977-4BE5-BA67-8EB186C03514}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Moq.Tests", "tests\Moq.Tests\Moq.Tests.csproj", "{81BBC911-4916-4E10-A955-752AE47CB2B9}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Moq.Tests", "src\Moq.Tests\Moq.Tests.csproj", "{81BBC911-4916-4E10-A955-752AE47CB2B9}" EndProject -Project("{778DAE3C-4631-46EA-AA77-85C1314464D9}") = "Moq.Tests.VisualBasic", "tests\Moq.Tests.VisualBasic\Moq.Tests.VisualBasic.vbproj", "{840A8B2E-3D4B-4521-A61A-0291562CDC8B}" +Project("{778DAE3C-4631-46EA-AA77-85C1314464D9}") = "Moq.Tests.VisualBasic", "src\Moq.Tests.VisualBasic\Moq.Tests.VisualBasic.vbproj", "{840A8B2E-3D4B-4521-A61A-0291562CDC8B}" EndProject -Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Moq.Tests.FSharpTypes", "tests\Moq.Tests.FSharpTypes\Moq.Tests.FSharpTypes.fsproj", "{2D9EE4E0-8433-4F9C-A330-C4D74B956E0B}" +Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "Moq.Tests.FSharpTypes", "src\Moq.Tests.FSharpTypes\Moq.Tests.FSharpTypes.fsproj", "{2D9EE4E0-8433-4F9C-A330-C4D74B956E0B}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/_config.yml b/_config.yml new file mode 100644 index 000000000..a61f7e0bf --- /dev/null +++ b/_config.yml @@ -0,0 +1,3 @@ +theme: jekyll-theme-slate + +exclude: [ 'src/', '*.sln', 'Gemfile*', '*.rsp' ] \ No newline at end of file diff --git a/appveyor.yml b/appveyor.yml deleted file mode 100644 index 34b318e3e..000000000 --- a/appveyor.yml +++ /dev/null @@ -1,36 +0,0 @@ -image: Visual Studio 2022 - -init: - - git config --global core.autocrlf input - -environment: - MSBUILD_LOGGER: C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll - SN_EXE: C:\Program Files (x86)\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.7 Tools\x64\sn.exe - SNK_FILE: Moq.snk - FSHARPTYPES_DLL: .\tests\Moq.Tests\bin\Release\net472\Moq.Tests.FSharpTypes.dll - -build_script: - - dotnet restore Moq.sln # --logger:"%MSBUILD_LOGGER%" - - dotnet build Moq.sln --configuration Release --no-restore # --logger:"%MSBUILD_LOGGER%" - -before_test: - - call "%SN_EXE%" -R %FSHARPTYPES_DLL% %SNK_FILE% - -test_script: - - dotnet test --no-build --configuration Release .\tests\Moq.Tests\Moq.Tests.csproj # --logger:"%MSBUILD_LOGGER%" - -after_test: - - dotnet pack Moq.sln --no-build --no-restore --configuration Release --output out # --logger:"%MSBUILD_LOGGER%" - -deploy: - - provider: NuGet - api_key: - secure: 5dsPohRA1/Bm/yD1DTUDZ5ZUAH7+jjWWcy6wnFZrWDKG4C8VOInfdWj/71SZgHYG - on: - appveyor_repo_tag: true - -nuget: - project_feed: true - -artifacts: - - path: 'out\*.nupkg' diff --git a/assets/css/style.scss b/assets/css/style.scss new file mode 100644 index 000000000..5e165a3ca --- /dev/null +++ b/assets/css/style.scss @@ -0,0 +1,26 @@ +--- +--- + +@import "jekyll-theme-slate"; + +.inner { + max-width: 960px; +} + +pre, code { + background-color: unset; + font-size: unset; +} + + code { + font-size: 0.80em; +} + +h1 > img { + border: unset; + box-shadow: unset; + vertical-align: middle; + -moz-box-shadow: unset; + -o-box-shadow: unset; + -ms-box-shadow: unset; +} diff --git a/moq-bigger.png b/assets/img/moq-bigger.png similarity index 100% rename from moq-bigger.png rename to assets/img/moq-bigger.png diff --git a/moq-icon.png b/assets/img/moq-icon.png similarity index 100% rename from moq-icon.png rename to assets/img/moq-icon.png diff --git a/moq-small.png b/assets/img/moq-small.png similarity index 100% rename from moq-small.png rename to assets/img/moq-small.png diff --git a/moq.png b/assets/img/moq.png similarity index 100% rename from moq.png rename to assets/img/moq.png diff --git a/build/AssemblyInfo.props b/build/AssemblyInfo.props deleted file mode 100644 index 9899c3eda..000000000 --- a/build/AssemblyInfo.props +++ /dev/null @@ -1,11 +0,0 @@ - - - - $(MSBuildAllProjects);$(MSBuildThisFileFullPath) - True - $(MSBuildProjectName) - Moq - Clarius Consulting, Manas Technology Solutions, InSTEDD, and Contributors - - - diff --git a/build/GitVersion.props b/build/GitVersion.props deleted file mode 100644 index 78ae6e9e5..000000000 --- a/build/GitVersion.props +++ /dev/null @@ -1,30 +0,0 @@ - - - - $(MSBuildAllProjects);$(MSBuildThisFileFullPath) - True - True - False - - - - - - - - - $(GitBaseVersionMajor).$(GitBaseVersionMinor).0.0 - $(GitBaseVersionMajor).$(GitBaseVersionMinor).$(GitBaseVersionPatch).$(GitCommits) - $(GitBaseVersionMajor).$(GitBaseVersionMinor).$(GitBaseVersionPatch)$(GitSemVerDashLabel)+sha.$(GitCommit) - $(GitBaseVersionMajor).$(GitBaseVersionMinor).$(GitBaseVersionPatch).$(GitCommits)$(GitSemVerDashLabel)+sha.$(GitCommit) - - - - - - $(GitSemVerMajor).$(GitSemVerMinor).$(GitSemVerPatch)$(GitSemVerDashLabel) - configuration=$(Configuration);version=$(PackageVersion) - - - - diff --git a/build/SignAssembly.props b/build/SignAssembly.props deleted file mode 100644 index 732e172fd..000000000 --- a/build/SignAssembly.props +++ /dev/null @@ -1,11 +0,0 @@ - - - - $(MSBuildAllProjects);$(MSBuildThisFileFullPath) - True - False - False - $(RootDirectory)Moq.snk - - - diff --git a/build/SourceLink.props b/build/SourceLink.props deleted file mode 100644 index e36e2b217..000000000 --- a/build/SourceLink.props +++ /dev/null @@ -1,12 +0,0 @@ - - - - $(MSBuildAllProjects);$(MSBuildThisFileFullPath) - True - - - - - - - diff --git a/build/xUnit.props b/build/xUnit.props deleted file mode 100644 index 3872eb94f..000000000 --- a/build/xUnit.props +++ /dev/null @@ -1,18 +0,0 @@ - - - - $(MSBuildAllProjects);$(MSBuildThisFileFullPath) - - - - - - - - - - - - - diff --git a/src/Directory.Build.props b/src/Directory.Build.props new file mode 100644 index 000000000..6b9a668f1 --- /dev/null +++ b/src/Directory.Build.props @@ -0,0 +1,146 @@ + + + + + false + + true + + + + + $(CI) + + + + Daniel Cazzulino + Copyright (C) Daniel Cazzulino and Contributors. All rights reserved. + false + MIT + + + icon.png + readme.md + + icon.png + readme.md + + true + true + + $([System.IO.Path]::GetFullPath('$(MSBuildThisFileDirectory)..\bin')) + + + true + + + true + + + + Release + true + false + Latest + + + false + + embedded + true + enable + + strict + + + $(MSBuildProjectName) + $(MSBuildProjectName.IndexOf('.')) + $(MSBuildProjectName.Substring(0, $(RootNamespaceDot))) + + + $(DefaultItemExcludes);*.binlog;*.zip;*.rsp;*.items;**/TestResults/**/*.* + + true + true + true + true + + + true + + + false + + + NU5105;$(NoWarn) + + true + + + true + + + LatestMinor + + + + + $(MSBuildThisFileDirectory)kzu.snk + + 002400000480000094000000060200000024000052534131000400000100010051155fd0ee280be78d81cc979423f1129ec5dd28edce9cd94fd679890639cad54c121ebdb606f8659659cd313d3b3db7fa41e2271158dd602bb0039a142717117fa1f63d93a2d288a1c2f920ec05c4858d344a45d48ebd31c1368ab783596b382b611d8c92f9c1b3d338296aa21b12f3bc9f34de87756100c172c52a24bad2db + 00352124762f2aa5 + true + + + + + 42.42.42 + + + + <_VersionLabel>$(VersionLabel.Replace('refs/heads/', '')) + + <_VersionLabel Condition="$(_VersionLabel.Contains('refs/pull/'))">$(VersionLabel.TrimEnd('.0123456789')) + + <_VersionLabel>$(_VersionLabel.Replace('refs/pull/', 'pr')) + + <_VersionLabel>$(_VersionLabel.Replace('/merge', '')) + + <_VersionLabel>$(_VersionLabel.Replace('/', '-')) + + + $(_VersionLabel) + + + + + + + + + + + + + + + + diff --git a/src/Directory.Build.targets b/src/Directory.Build.targets new file mode 100644 index 000000000..5bd401967 --- /dev/null +++ b/src/Directory.Build.targets @@ -0,0 +1,178 @@ + + + + + CI;$(DefineConstants) + + + + true + true + + + + + false + true + + + + + + + + + + + + + + + + + + + 1.0.0 + $(VersionPrefix)-$(VersionSuffix) + $(VersionPrefix) + + + + + $(PackFolder) + $(PackFolderPath.Replace('\$(TargetFramework)', '')) + $(IntermediateOutputPath)$(PackFolderPath)\ + $(OutputPath)$(PackFolderPath)\ + $(OutputPath) + + + + + pr$(GITHUB_REF.Replace('refs/pull/', '').Replace('/merge', '')) + $(GITHUB_REF.Replace('refs/heads/', '').Replace('refs/tags/', '')) + + $(BUILD_SOURCEBRANCH.Replace('refs/heads/', '').Replace('refs/tags/', '')) + + pr$(APPVEYOR_PULL_REQUEST_NUMBER) + $(APPVEYOR_REPO_TAG_NAME) + $(APPVEYOR_REPO_BRANCH) + + $(TEAMCITY_BUILD_BRANCH) + + pr$(TRAVIS_PULL_REQUEST) + $(TRAVIS_BRANCH) + + pr$(CIRCLE_PR_NUMBER) + $(CIRCLE_TAG) + $(CIRCLE_BRANCH) + + $(CI_COMMIT_TAG) + pr$(CI_MERGE_REQUEST_IID) + pr$(CI_EXTERNAL_PULL_REQUEST_IID) + $(CI_COMMIT_BRANCH) + + pr$(BUDDY_EXECUTION_PULL_REQUEST_NO) + $(BUDDY_EXECUTION_TAG) + $(BUDDY_EXECUTION_BRANCH) + + + + + PrepareResources;$(CoreCompileDependsOn) + + + + + + + + + + MSBuild:Compile + $(IntermediateOutputPath)\$([MSBuild]::ValueOrDefault('%(RelativeDir)', '').Replace('\', '.').Replace('/', '.'))%(Filename).g$(DefaultLanguageSourceExtension) + $(Language) + $(RootNamespace) + $(RootNamespace).$([MSBuild]::ValueOrDefault('%(RelativeDir)', '').Replace('\', '.').Replace('/', '.').TrimEnd('.')) + %(Filename) + + + + + + + + + + + + + + + + + + + + + + + + $(PrivateRepositoryUrl) + + + + $(SourceRevisionId) + $(SourceRevisionId.Substring(0, 9)) + + $(RepositorySha) + + + + + <_GitSourceRoot Include="@(SourceRoot -> WithMetadataValue('SourceControl', 'git'))" /> + + + + @(_GitSourceRoot) + + + + + + + $(RepositoryUrl) + $(Description) + $(RepositoryUrl)/blob/main/changelog.md + + + + + + + + diff --git a/src/Directory.props b/src/Directory.props new file mode 100644 index 000000000..e242467ce --- /dev/null +++ b/src/Directory.props @@ -0,0 +1,15 @@ + + + + annotations + Moq + Clarius Consulting, Manas Technology Solutions, InSTEDD, and Contributors + $(MSBuildThisFileDirectory)Moq.snk + true + 00240000048000009400000006020000002400005253413100040000010001009f7a95086500f8f66d892174803850fed9c22225c2ccfff21f39c8af8abfa5415b1664efd0d8e0a6f7f2513b1c11659bd84723dc7900c3d481b833a73a2bcf1ed94c16c4be64d54352c86956c89930444e9ac15124d3693e3f029818e8410f167399d6b995324b635e95353ba97bfab856abbaeb9b40c9b160070c6325e22ddc + 69f491c39445e920 + + $(MSBuildThisFileDirectory)..\ + + + diff --git a/tests/Moq.Tests.ComTypes/BuildDLLFromIDL.cmd b/src/Moq.Tests.ComTypes/BuildDLLFromIDL.cmd similarity index 100% rename from tests/Moq.Tests.ComTypes/BuildDLLFromIDL.cmd rename to src/Moq.Tests.ComTypes/BuildDLLFromIDL.cmd diff --git a/tests/Moq.Tests.ComTypes/ComTypes.idl b/src/Moq.Tests.ComTypes/ComTypes.idl similarity index 100% rename from tests/Moq.Tests.ComTypes/ComTypes.idl rename to src/Moq.Tests.ComTypes/ComTypes.idl diff --git a/tests/Moq.Tests.ComTypes/Moq.Tests.ComTypes.dll b/src/Moq.Tests.ComTypes/Moq.Tests.ComTypes.dll similarity index 100% rename from tests/Moq.Tests.ComTypes/Moq.Tests.ComTypes.dll rename to src/Moq.Tests.ComTypes/Moq.Tests.ComTypes.dll diff --git a/tests/Moq.Tests.ComTypes/README.md b/src/Moq.Tests.ComTypes/README.md similarity index 100% rename from tests/Moq.Tests.ComTypes/README.md rename to src/Moq.Tests.ComTypes/README.md diff --git a/tests/Moq.Tests.FSharpTypes/HasAbstractActionEvent.fs b/src/Moq.Tests.FSharpTypes/HasAbstractActionEvent.fs similarity index 100% rename from tests/Moq.Tests.FSharpTypes/HasAbstractActionEvent.fs rename to src/Moq.Tests.FSharpTypes/HasAbstractActionEvent.fs diff --git a/tests/Moq.Tests.FSharpTypes/HasAbstractEventHandlerEvent.fs b/src/Moq.Tests.FSharpTypes/HasAbstractEventHandlerEvent.fs similarity index 100% rename from tests/Moq.Tests.FSharpTypes/HasAbstractEventHandlerEvent.fs rename to src/Moq.Tests.FSharpTypes/HasAbstractEventHandlerEvent.fs diff --git a/tests/Moq.Tests.FSharpTypes/HasAbstractIndexer.fs b/src/Moq.Tests.FSharpTypes/HasAbstractIndexer.fs similarity index 100% rename from tests/Moq.Tests.FSharpTypes/HasAbstractIndexer.fs rename to src/Moq.Tests.FSharpTypes/HasAbstractIndexer.fs diff --git a/tests/Moq.Tests.FSharpTypes/HasAbstractProperty.fs b/src/Moq.Tests.FSharpTypes/HasAbstractProperty.fs similarity index 100% rename from tests/Moq.Tests.FSharpTypes/HasAbstractProperty.fs rename to src/Moq.Tests.FSharpTypes/HasAbstractProperty.fs diff --git a/tests/Moq.Tests.FSharpTypes/HasActionEvent.fs b/src/Moq.Tests.FSharpTypes/HasActionEvent.fs similarity index 100% rename from tests/Moq.Tests.FSharpTypes/HasActionEvent.fs rename to src/Moq.Tests.FSharpTypes/HasActionEvent.fs diff --git a/tests/Moq.Tests.FSharpTypes/HasIndexer.fs b/src/Moq.Tests.FSharpTypes/HasIndexer.fs similarity index 100% rename from tests/Moq.Tests.FSharpTypes/HasIndexer.fs rename to src/Moq.Tests.FSharpTypes/HasIndexer.fs diff --git a/tests/Moq.Tests.FSharpTypes/HasProperty.fs b/src/Moq.Tests.FSharpTypes/HasProperty.fs similarity index 100% rename from tests/Moq.Tests.FSharpTypes/HasProperty.fs rename to src/Moq.Tests.FSharpTypes/HasProperty.fs diff --git a/tests/Moq.Tests.FSharpTypes/IHasActionEvent.fs b/src/Moq.Tests.FSharpTypes/IHasActionEvent.fs similarity index 100% rename from tests/Moq.Tests.FSharpTypes/IHasActionEvent.fs rename to src/Moq.Tests.FSharpTypes/IHasActionEvent.fs diff --git a/tests/Moq.Tests.FSharpTypes/IHasEventHandlerEvent.fs b/src/Moq.Tests.FSharpTypes/IHasEventHandlerEvent.fs similarity index 100% rename from tests/Moq.Tests.FSharpTypes/IHasEventHandlerEvent.fs rename to src/Moq.Tests.FSharpTypes/IHasEventHandlerEvent.fs diff --git a/tests/Moq.Tests.FSharpTypes/IHasIndexer.fs b/src/Moq.Tests.FSharpTypes/IHasIndexer.fs similarity index 100% rename from tests/Moq.Tests.FSharpTypes/IHasIndexer.fs rename to src/Moq.Tests.FSharpTypes/IHasIndexer.fs diff --git a/tests/Moq.Tests.FSharpTypes/IHasProperty.fs b/src/Moq.Tests.FSharpTypes/IHasProperty.fs similarity index 100% rename from tests/Moq.Tests.FSharpTypes/IHasProperty.fs rename to src/Moq.Tests.FSharpTypes/IHasProperty.fs diff --git a/tests/Moq.Tests.FSharpTypes/Moq.Tests.FSharpTypes.fsproj b/src/Moq.Tests.FSharpTypes/Moq.Tests.FSharpTypes.fsproj similarity index 66% rename from tests/Moq.Tests.FSharpTypes/Moq.Tests.FSharpTypes.fsproj rename to src/Moq.Tests.FSharpTypes/Moq.Tests.FSharpTypes.fsproj index fc94860d7..862a558be 100644 --- a/tests/Moq.Tests.FSharpTypes/Moq.Tests.FSharpTypes.fsproj +++ b/src/Moq.Tests.FSharpTypes/Moq.Tests.FSharpTypes.fsproj @@ -1,17 +1,17 @@ - - - - +ο»Ώ - net472;netcoreapp3.1;net6.0 + net472;net6.0 True portable False - + + + + diff --git a/src/Moq.Tests.VisualBasic/IssueReports.vb b/src/Moq.Tests.VisualBasic/IssueReports.vb new file mode 100644 index 000000000..fd2274bcb --- /dev/null +++ b/src/Moq.Tests.VisualBasic/IssueReports.vb @@ -0,0 +1,127 @@ +' Copyright (c) 2007, Clarius Consulting, Manas Technology Solutions, InSTEDD, and Contributors. +' All rights reserved. Licensed under the BSD 3-Clause License; see License.txt. + +Imports Xunit + +Public Class IssueReports + + Class Issue278 + + + Sub SetupsForPropertiesWithMultipleArgsDoNotOverwriteEachOther() + Dim mock As New Mock(Of ISimpleInterface)() + + mock.Setup(Function(m) m.PropertyWithMultipleArgs(1, 1)).Returns(1) + mock.Setup(Function(m) m.PropertyWithMultipleArgs(1, 2)).Returns(2) + + Assert.Equal(1, mock.Object.PropertyWithMultipleArgs(1, 1)) + Assert.Equal(2, mock.Object.PropertyWithMultipleArgs(1, 2)) + + End Sub + + Interface ISimpleInterface + + ReadOnly Property PropertyWithMultipleArgs(setting As Integer, setting2 As Integer) As Integer + + End Interface + End Class + + Class Issue1067 + + + Sub Test_NonGeneric() + Dim userManagerMock = New Mock(Of IUserManager)() + Setup_NonGeneric(userManagerMock, 42) + + Dim user As New User() + userManagerMock.Object.Create(user) + + Assert.Equal(42, user.Id) + End Sub + + + Sub Test_Generic() + Dim userManagerMock = New Mock(Of IUserManager)() + Setup_Generic(Of User)(userManagerMock, 42) + + Dim user As New User() + userManagerMock.Object.Create(user) + + Assert.Equal(42, user.Id) + End Sub + + Class User + Property Id As Integer + End Class + + Interface IUserManager + Sub Create(User As User) + End Interface + + Protected Sub Setup_NonGeneric(userManagerMock As Mock(Of IUserManager), expectedId As Integer) + userManagerMock.Setup(Sub(manager) manager.Create(It.IsAny(Of User))).Callback(Sub(user) user.Id = expectedId) + End Sub + + Protected Sub Setup_Generic(Of TUser As User)(userManagerMock As Mock(Of IUserManager), expectedId As Integer) + userManagerMock.Setup(Sub(manager) manager.Create(It.IsAny(Of TUser))).Callback(Sub(user) user.Id = expectedId) + ' ^ + ' The use of generics will cause the VB.NET compiler to wrap the `It.IsAny<>` call with two `Convert` nodes. + ' The inner conversion will convert to `Object`, and the outer conversion will convert to `User` (i.e. the type that + ' `TUser` is constrained to). `MatcherFactory` needs to be able to recognize the `It.IsAny<>` matcher even if it + ' is doubly wrapped! + End Sub + + End Class + + Class Issue1129 + + + Sub Test() + Dim classMock = New Mock(Of IndexerInterface)() + + classMock.SetupAllProperties() + + Assert.False(classMock.Object.Value) + End Sub + Interface IndexerInterface + ReadOnly Property SystemDefault() As Boolean + Property Value() As Boolean + Property Value(ByVal OverrideLevel As Integer) As Boolean + Property Value(ByVal OverrideLevel As Integer, ByVal OverrideID As String) As Boolean + End Interface + End Class + + Class Issue1153 + + + Sub Indexer_overload_can_be_distinguished_from_property_when_mocking_declaring_class() + Dim mock = New Mock(Of MyVBClassBase)() + mock.Setup(Function(m) m.Prop).Returns(True) + End Sub + + + Sub Indexer_overload_can_be_distinguished_from_property_when_mocking_subclass_of_declaring_class() + Dim mock = New Mock(Of MyVBClass)() + mock.Setup(Function(m) m.Prop).Returns(True) + End Sub + + Class MyVBClassBase + Overridable ReadOnly Property Prop() As Boolean + Get + Return True + End Get + End Property + Overridable ReadOnly Property Prop(ByVal userID As Guid) As Boolean + Get + Return False + End Get + End Property + End Class + + Class MyVBClass + Inherits MyVBClassBase + End Class + + End Class + +End Class diff --git a/src/Moq.Tests.VisualBasic/Moq.Tests.VisualBasic.vbproj b/src/Moq.Tests.VisualBasic/Moq.Tests.VisualBasic.vbproj new file mode 100644 index 000000000..31f679a49 --- /dev/null +++ b/src/Moq.Tests.VisualBasic/Moq.Tests.VisualBasic.vbproj @@ -0,0 +1,21 @@ +ο»Ώ + + + net472;net6.0 + True + portable + False + + + + + + + + + + + + + + diff --git a/src/Moq.Tests/ActionObserverFixture.cs b/src/Moq.Tests/ActionObserverFixture.cs new file mode 100644 index 000000000..d7b8f7a8f --- /dev/null +++ b/src/Moq.Tests/ActionObserverFixture.cs @@ -0,0 +1,466 @@ +// Copyright (c) 2007, Clarius Consulting, Manas Technology Solutions, InSTEDD, and Contributors. +// All rights reserved. Licensed under the BSD 3-Clause License; see License.txt. + +using System; +using System.Linq.Expressions; + +using Xunit; + +namespace Moq.Tests +{ + public class ActionObserverFixture + { + public class Reconstructibility + { + // NOTE: These tests might look pointless at first glance, until you notice + // the signature of `AssertReconstructable`: delegates are being compared to + // LINQ expression trees for equality. + + [Fact] + public void Void_method_call() + { + AssertReconstructable( + x => x.Void(), + x => x.Void()); + } + + [Fact] + public void Void_method_call_with_arg() + { + AssertReconstructable( + x => x.VoidWithInt(42), + x => x.VoidWithInt(42)); + } + + [Fact] + public void Void_method_call_with_coerced_arg() + { + AssertReconstructable( + x => x.VoidWithLong(42), + x => x.VoidWithLong(42)); + } + + [Fact] + public void Void_method_call_with_coerced_nullable_arg() + { + AssertReconstructable( + "x => x.VoidWithNullableInt(42)", + x => x.VoidWithNullableInt(42)); + } + + [Fact] + public void Void_method_call_with_cast_arg() + { + AssertReconstructable( + x => x.VoidWithInt((int)42L), + x => x.VoidWithInt((int)42L)); + } + + [Fact] + public void Void_method_call_with_arg_evaluated() + { + int arg = 42; + AssertReconstructable( + x => x.VoidWithInt(42), + x => x.VoidWithInt(arg)); + } + + [Fact] + public void Void_method_call_on_sub_object() + { + AssertReconstructable( + x => x.GetY().Z.Void(), + x => x.GetY().Z.Void()); + } + + [Fact] + public void Void_method_call_on_sub_with_several_args() + { + AssertReconstructable( + x => x.GetY(1).Z.VoidWithIntInt(2, 3), + x => x.GetY(1).Z.VoidWithIntInt(2, 3)); + } + + [Fact] + public void Void_method_call_with_matcher() + { + AssertReconstructable( + x => x.VoidWithInt(It.IsAny()), + x => x.VoidWithInt(It.IsAny())); + } + + [Fact] + public void Void_method_call_with_matcher_of_assignment_compatible_type() + { + AssertReconstructable( + x => x.VoidWithObject(It.IsAny()), + x => x.VoidWithObject(It.IsAny())); + } + + [Fact] + public void Void_method_call_with_matcher_in_first_of_three_invocations() + { + AssertReconstructable( + x => x.GetY(It.IsAny()).Z.VoidWithIntInt(0, 0), + x => x.GetY(It.IsAny()).Z.VoidWithIntInt(0, 0)); + } + + [Fact] + public void Void_method_call_with_matcher_in_third_of_three_invocations_1() + { + AssertReconstructable( + x => x.GetY(0).Z.VoidWithIntInt(1, It.IsAny()), + x => x.GetY(0).Z.VoidWithIntInt(1, It.IsAny())); + } + + [Fact] + public void Void_method_call_with_matcher_in_third_of_three_invocations_2() + { + AssertReconstructable( + x => x.GetY(0).Z.VoidWithIntInt(It.IsAny(), 2), + x => x.GetY(0).Z.VoidWithIntInt(It.IsAny(), 2)); + } + + [Fact] + public void Void_method_call_with_matcher_in_first_and_third_of_three_invocations() + { + AssertReconstructable( + "x => x.GetY(It.Is(i => i % 2 == 0)).Z.VoidWithIntInt(It.IsAny(), 2)", + x => x.GetY(It.Is(i => i % 2 == 0)).Z.VoidWithIntInt(It.IsAny(), 2)); + } + + [Fact] + public void Method_with_matchers_after_default_arg() + { + // This demonstrates that even though the first argument has a default value, + // the matcher isn't placed there, because it has a type (string) that won't fit (int). + + AssertReconstructable( + x => x.VoidWithIntString(0, It.IsAny()), + x => x.VoidWithIntString(0, It.IsAny())); + } + + [Fact] + public void Assignment() + { + AssertReconstructable( + "x => x.GetY().Z.Property = \"value\"", + x => x.GetY().Z.Property = "value"); + } + + [Fact] + public void Assignment_with_captured_var_on_rhs() + { + var arg = "value"; + AssertReconstructable( + "x => x.GetY().Z.Property = \"value\"", + x => x.GetY().Z.Property = arg); + } + + [Fact] + public void Assignment_with_matcher_on_rhs() + { + AssertReconstructable( + "x => x.GetY().Z.Property = It.IsAny()", + x => x.GetY().Z.Property = It.IsAny()); + } + + [Fact] + public void Indexer_assignment_with_arg() + { + AssertReconstructable( + "x => x[1] = null", + x => x[1] = null); + } + + [Fact] + public void Indexer_assignment_with_matcher_on_lhs_1() + { + AssertReconstructable( + "x => x[It.IsAny()] = null", + x => x[It.IsAny()] = null); + } + + [Fact] + public void Indexer_assignment_with_matcher_on_lhs_2() + { + AssertReconstructable( + "x => x[1, It.IsAny()] = 0", + x => x[1, It.IsAny()] = 0); + } + + [Fact] + public void Indexer_assignment_with_matcher_on_rhs() + { + AssertReconstructable( + "x => x[1] = It.IsAny()", + x => x[1] = It.IsAny()); + } + + [Fact] + public void Indexer_assignment_with_matchers_everywhere() + { + AssertReconstructable( + "x => x[It.Is(i => i == 0), It.Is(i => i == 2)] = It.Is(i => i == 3)", + x => x[It.Is(i => i == 0), It.Is(i => i == 2)] = It.Is(i => i == 3)); + } + + [Fact] + public void Widening_and_narrowing_and_enum_convertions() + { + ushort arg = 123; + AssertReconstructable( + "x => x.VoidWithShort(123)", + x => x.VoidWithShort((short)arg)); + AssertReconstructable( + "x => x.VoidWithInt(123)", + x => x.VoidWithInt(arg)); + AssertReconstructable( + "x => x.VoidWithLong(123)", + x => x.VoidWithLong(arg)); + + long longArg = 654L; + AssertReconstructable( + "x => x.VoidWithShort(654)", + x => x.VoidWithShort((short)longArg)); + + AssertReconstructable( + "x => x.VoidWithObject(ActionObserverFixture.Reconstructibility.Color.Green)", + x => x.VoidWithObject(Color.Green)); + AssertReconstructable( + "x => x.VoidWithEnum(ActionObserverFixture.Reconstructibility.Color.Green)", + x => x.VoidWithEnum(Color.Green)); + AssertReconstructable( + "x => x.VoidWithNullableEnum(ActionObserverFixture.Reconstructibility.Color.Green)", + x => x.VoidWithNullableEnum(Color.Green)); + } + + [Fact] + public void It_IsAny_enum_converted_and_assigned_to_int_parameter() + { + AssertReconstructable( + x => x.VoidWithInt((int)It.IsAny()), + x => x.VoidWithInt((int)It.IsAny())); + } + + [Fact] + public void It_IsAny_short_widened_to_int_parameter() + { + AssertReconstructable( + x => x.VoidWithInt(It.IsAny()), + x => x.VoidWithInt(It.IsAny())); + + /* Unmerged change from project 'Moq.Tests(net6.0)' + Before: + private void AssertReconstructable(string expected, Action action) + After: + void AssertReconstructable(string expected, Action action) + */ + } + + void AssertReconstructable(string expected, Action action) + { + Expression actual = ActionObserver.Instance.ReconstructExpression(action); + actual = PrepareForComparison.Instance.Visit(actual); + Assert.Equal(expected, actual.ToStringFixed()); + + /* Unmerged change from project 'Moq.Tests(net6.0)' + Before: + private void AssertReconstructable(Expression> expected, Action action) + After: + void AssertReconstructable(Expression> expected, Action action) + */ + } + + void AssertReconstructable(Expression> expected, Action action) + { + Expression actual = ActionObserver.Instance.ReconstructExpression(action); + expected = (Expression>)PrepareForComparison.Instance.Visit(expected); + actual = PrepareForComparison.Instance.Visit(actual); + Assert.Equal(expected, actual, ExpressionComparer.Default); + } + + public interface IX + { + IY this[int index] { get; set; } + int this[int index1, int index2] { get; set; } + IY GetY(); + IY GetY(int arg); + void Void(); + void VoidWithShort(short arg); + void VoidWithInt(int arg); + void VoidWithLong(long arg); + void VoidWithNullableInt(int? arg); + void VoidWithIntString(int arg1, string arg2); + void VoidWithObject(object arg); + void VoidWithEnum(Color arg); + void VoidWithNullableEnum(Color? arg); + } + + public interface IY + { + IZ Z { get; } + } + + public interface IZ + { + object Property { get; set; } + void Void(); + void VoidWithIntInt(int arg1, int arg2); + } + + public enum Color + { + Red, + Green, + Blue, + Yellow, + } + } + + public class Error_detection + { + [Fact] + public void Stops_before_non_interceptable_method() + { + AssertFailsAfter("x => x...", x => x.NonVirtual()); + } + + [Fact] + public void Stops_before_non_interceptable_property() + { + AssertFailsAfter("x => x...", x => x.NonVirtualProperty = It.IsAny()); + } + + [Fact] + public void Stops_after_non_interceptable_return_type() + { + AssertFailsAfter("x => x.SealedY...", x => x.SealedY.Method()); + + /* Unmerged change from project 'Moq.Tests(net6.0)' + Before: + private void AssertFailsAfter(string expectedPartial, Action action) + After: + void AssertFailsAfter(string expectedPartial, Action action) + */ + } + + void AssertFailsAfter(string expectedPartial, Action action) + { + var error = Assert.Throws(() => ActionObserver.Instance.ReconstructExpression(action)); + Assert.Contains($": {expectedPartial}", error.Message); + } + + public interface IX + { + IY Y { get; } + SealedY SealedY { get; } + } + + public class X + { + public IY NonVirtualProperty { get; set; } + public void NonVirtual() { } + } + + public interface IY + { + void Method(int arg1, int arg2); + } + + public sealed class SealedY + { + public void Method() { } + } + } + + // These tests document limitations of the current implementation. + public class Limitations + { + [Fact] + public void Method_with_matchers_after_default_arg() + { + // This is because parameters with default values are filled from left to right. + + AssertIncorrectlyReconstructsAs( + x => x.Method(It.IsAny(), 0), + x => x.Method(0, It.IsAny())); + } + + [Fact] + public void Indexer_with_default_value_on_lfs_and_matcher_on_rhs_both_having_same_types() + { + // Same as above, since LHS and RHS are actually both part of a single parameter list of a method call `get_Item(...lhs, rhs). + AssertIncorrectlyReconstructsAs( + "x => x[It.IsAny()] = 0", + x => x[0] = It.IsAny()); + + /* Unmerged change from project 'Moq.Tests(net6.0)' + Before: + private void AssertIncorrectlyReconstructsAs(string expected, Action action) + After: + void AssertIncorrectlyReconstructsAs(string expected, Action action) + */ + } + + void AssertIncorrectlyReconstructsAs(string expected, Action action) + { + Expression actual = ActionObserver.Instance.ReconstructExpression(action); + actual = PrepareForComparison.Instance.Visit(actual); + Assert.Equal(expected, actual.ToStringFixed()); + + /* Unmerged change from project 'Moq.Tests(net6.0)' + Before: + private void AssertIncorrectlyReconstructsAs(Expression> expected, Action action) + After: + void AssertIncorrectlyReconstructsAs(Expression> expected, Action action) + */ + } + + void AssertIncorrectlyReconstructsAs(Expression> expected, Action action) + { + Expression actual = ActionObserver.Instance.ReconstructExpression(action); + expected = (Expression>)PrepareForComparison.Instance.Visit(expected); + actual = PrepareForComparison.Instance.Visit(actual); + Assert.Equal(expected, actual, ExpressionComparer.Default); + } + + public interface IX + { + int this[int index] { get; set; } + void Method(int arg1, int arg2); + + /* Unmerged change from project 'Moq.Tests(net6.0)' + Before: + private sealed class PrepareForComparison : ExpressionVisitor + After: + sealed class PrepareForComparison : ExpressionVisitor + */ + } + } + + // The expression trees reconstructed by `ActionObserver` may differ from those + // produced by the Roslyn compilers in some minor regards that shouldn't actually + // matter to program execution; however `ExpressionComparer` will notice the + // differences, making above tests fail. Because of this, we try to "equalize" + // expressions created by the Roslyn compilers (`expected`) and those produced + // by `ActionObserver` (`actual`) using this expression visitor: + sealed class PrepareForComparison : ExpressionVisitor + { + public static readonly PrepareForComparison Instance = new PrepareForComparison(); + + protected override Expression VisitExtension(Expression node) + { + if (node is MatchExpression me) + { + // Resolve `MatchExpression`s to their matcher's `RenderExpression`: + return me.Match.RenderExpression; + } + else + { + return base.VisitExtension(node); + } + } + } + } +} diff --git a/src/Moq.Tests/AfterReturnCallbackDelegateValidationFixture.cs b/src/Moq.Tests/AfterReturnCallbackDelegateValidationFixture.cs new file mode 100644 index 000000000..1ebf70c3e --- /dev/null +++ b/src/Moq.Tests/AfterReturnCallbackDelegateValidationFixture.cs @@ -0,0 +1,103 @@ +// Copyright (c) 2007, Clarius Consulting, Manas Technology Solutions, InSTEDD, and Contributors. +// All rights reserved. Licensed under the BSD 3-Clause License; see License.txt. + +using System; + +using Moq.Language.Flow; + +using Xunit; + +namespace Moq.Tests +{ + public class AfterReturnCallbackDelegateValidationFixture + + /* Unmerged change from project 'Moq.Tests(net6.0)' + Before: + private readonly ISetup setup; + After: + readonly ISetup setup; + */ + { + readonly ISetup setup; + + public AfterReturnCallbackDelegateValidationFixture() + { + this.setup = new Mock().Setup(m => m.Method(It.IsAny(), It.IsAny())); + } + + [Fact] + public void Callback_before_Returns__delegate_may_not_be_null() + { + var setup = this.setup; + Assert.Throws(() => setup.Callback(null)); + } + + [Fact] + public void Callback_after_Returns__delegate_may_not_be_null() + { + var setup = this.setup.Returns(true); + Assert.Throws(() => setup.Callback(null)); + } + + [Fact] + public void Callback_before_Returns__delegate_may_completely_omit_parameters() + { + var setup = this.setup; + setup.Callback(() => { }); + } + + [Fact] + public void Callback_after_Returns__delegate_may_completely_omit_parameters() + { + var setup = this.setup.Returns(true); + setup.Callback(() => { }); + } + + [Fact] + public void Callback_before_Returns__delegate_may_not_partially_omit_parameters() + { + var setup = this.setup; + Assert.Throws(() => setup.Callback((string arg1) => { })); + } + + [Fact] + public void Callback_after_Returns__delegate_may_not_partially_omit_parameters() + { + var setup = this.setup.Returns(true); + Assert.Throws(() => setup.Callback((string arg1) => { })); + } + + [Fact] + public void Callback_before_Returns__delegate_may_use_less_specific_parameter_types() + { + var setup = this.setup; + setup.Callback((object arg1, object arg2) => { }); + } + + [Fact] + public void Callback_after_Returns__delegate_may_use_less_specific_parameter_types() + { + var setup = this.setup.Returns(true); + setup.Callback((object arg1, object arg2) => { }); + } + + [Fact] + public void Callback_before_Returns__delegate_may_not_use_more_specific_parameter_types() + { + var setup = this.setup; + Assert.Throws(() => setup.Callback((string arg1, string arg2) => { })); + } + + [Fact] + public void Callback_after_Returns__delegate_may_not_use_more_specific_parameter_types() + { + var setup = this.setup.Returns(true); + Assert.Throws(() => setup.Callback((string arg1, string arg2) => { })); + } + + public interface IFoo + { + bool Method(string arg1, object arg2); + } + } +} diff --git a/src/Moq.Tests/AsInterfaceFixture.cs b/src/Moq.Tests/AsInterfaceFixture.cs new file mode 100644 index 000000000..5b55b71a1 --- /dev/null +++ b/src/Moq.Tests/AsInterfaceFixture.cs @@ -0,0 +1,271 @@ +// Copyright (c) 2007, Clarius Consulting, Manas Technology Solutions, InSTEDD, and Contributors. +// All rights reserved. Licensed under the BSD 3-Clause License; see License.txt. + +using System; + +using Xunit; + +namespace Moq.Tests +{ + public class AsInterfaceFixture + { + [Fact] + public void ShouldThrowIfAsIsInvokedAfterInstanceIsRetrieved() + { + var mock = new Mock(); + + var instance = mock.Object; + + Assert.Throws(() => mock.As()); + } + + [Fact] + public void ShouldThrowIfAsIsInvokedWithANonInterfaceTypeParameter() + { + var mock = new Mock(); + + Assert.Throws(() => mock.As()); + } + + [Fact] + public void ShouldExpectGetOnANewInterface() + { + var mock = new Mock(); + + bool called = false; + + mock.As().SetupGet(x => x.Value) + .Callback(() => called = true) + .Returns(25); + + Assert.Equal(25, ((IFoo)mock.Object).Value); + Assert.True(called); + } + + [Fact] + public void ShouldExpectCallWithArgumentOnNewInterface() + { + var mock = new Mock(); + mock.As().Setup(x => x.Execute("ping")).Returns("ack"); + + Assert.Equal("ack", ((IFoo)mock.Object).Execute("ping")); + } + + [Fact] + public void ShouldExpectPropertySetterOnNewInterface() + { + bool called = false; + int value = 0; + var mock = new Mock(); + mock.As().SetupSet(x => x.Value = 100).Callback(i => { value = i; called = true; }); + + ((IFoo)mock.Object).Value = 100; + + Assert.Equal(100, value); + Assert.True(called); + } + + [Fact] + public void MockAsExistingInterfaceAfterObjectSucceedsIfNotNew() + { + var mock = new Mock(); + + mock.As().SetupGet(x => x.Value).Returns(25); + + Assert.Equal(25, ((IFoo)mock.Object).Value); + + var fm = mock.As(); + + fm.Setup(f => f.Execute()); + } + + [Fact] + public void ThrowsWithMockedTypeName() + { + var bag = new Mock(); + var foo = bag.As(); + + bag.Setup(b => b.Add("foo", "bar")).Verifiable(); + foo.Setup(f => f.Execute()).Verifiable(); + + var me = Assert.Throws(() => bag.Verify()); + Assert.True(me.IsVerificationError); + Assert.Contains(typeof(IFoo).Name, me.Message); + Assert.Contains(typeof(IBag).Name, me.Message); + } + + [Fact] + public void GetMockFromAddedInterfaceWorks() + { + var bag = new Mock(); + var foo = bag.As(); + + foo.SetupGet(x => x.Value).Returns(25); + + IFoo f = bag.Object as IFoo; + + var foomock = Mock.Get(f); + + Assert.NotNull(foomock); + } + + [Fact] + public void GetMockFromNonAddedInterfaceThrows() + { + var bag = new Mock(); + bag.As(); + bag.As(); + object b = bag.Object; + + Assert.Throws(() => Mock.Get(b)); + } + + [Fact] + public void VerifiesExpectationOnAddedInterface() + { + var bag = new Mock(); + var foo = bag.As(); + + foo.Setup(f => f.Execute()).Verifiable(); + + var ex = Assert.Throws(() => foo.Verify()); + Assert.True(ex.IsVerificationError); + + ex = Assert.Throws(() => foo.VerifyAll()); + Assert.True(ex.IsVerificationError); + + Assert.Throws(() => foo.Verify(f => f.Execute())); + + foo.Object.Execute(); + + foo.Verify(); + foo.VerifyAll(); + foo.Verify(f => f.Execute()); + } + + [Fact] + public void VerifiesExpectationOnAddedInterfaceCastedDynamically() + { + var bag = new Mock(); + bag.As(); + + ((IFoo)bag.Object).Execute(); + + bag.As().Verify(f => f.Execute()); + } + + [Fact] + public void ShouldBeAbleToCastToImplementedInterface() + { + var fooBar = new Mock(); + var obj = fooBar.Object; + fooBar.As(); + } + + [Fact] + public void ShouldNotThrowIfCallExplicitlyImplementedInterfacesMethodWhenCallBaseIsTrue() + { + var fooBar = new Mock(); + fooBar.CallBase = true; + var bag = (IBag)fooBar.Object; + bag.Get("test"); + } + + [Fact] + public void Setup_targets_method_implementing_interface_not_other_method_with_same_signature() + { + var mock = new Mock() { CallBase = true }; + mock.As().Setup(m => m.GetValue()).Returns(3); + + // The public method should have been left alone since it's not the one implementing `IService`: + var valueOfOtherMethod = mock.Object.GetValue(); + Assert.Equal(1, valueOfOtherMethod); + + // The method implementing the interface method should have be mocked: + var valueOfSetupMethod = ((IService)mock.Object).GetValue(); + Assert.Equal(3, valueOfSetupMethod); + } + + [Fact] + public void As_mocked_type_returns_original_mock() + { + Mock mock = new Mock(); + Assert.Same(mock, mock.As()); + } + + [Fact] + public void Can_roundtrip_to_original_interface_mock_via_Mock_Get_and_As_original_interface() + { + Mock bMockOriginal = new Mock(); + A a = bMockOriginal.Object; + Mock aMock = Mock.Get(a); + Mock bMockRoundtripped = aMock.As(); + Assert.Same(bMockOriginal, bMockRoundtripped); + } + + public interface A { } + public interface B : A { } + + public class Service : IService + { + public virtual int GetValue() => 1; + + int IService.GetValue() => 2; + } + + public interface IService + { + int GetValue(); + } + + // see also test fixture `Issue458` in `IssueReportsFixture` + + public interface IFoo + { + void Execute(); + string Execute(string command); + string Execute(string arg1, string arg2); + string Execute(string arg1, string arg2, string arg3); + string Execute(string arg1, string arg2, string arg3, string arg4); + + int Value { get; set; } + } + + public interface IBag + { + void Add(string key, object o); + object Get(string key); + } + + internal interface IBar + { + void Test(); + } + + public abstract class FooBar : IFoo, IBag, IBar + { + public abstract void Execute(); + + public abstract string Execute(string command); + + public abstract string Execute(string arg1, string arg2); + + public abstract string Execute(string arg1, string arg2, string arg3); + + public abstract string Execute(string arg1, string arg2, string arg3, string arg4); + + public abstract int Value { get; set; } + + void IBag.Add(string key, object o) + { + } + + object IBag.Get(string key) + { + return null; + } + + public abstract void Test(); + } + } +} diff --git a/src/Moq.Tests/Async/AwaitableFixture.cs b/src/Moq.Tests/Async/AwaitableFixture.cs new file mode 100644 index 000000000..dd2643c46 --- /dev/null +++ b/src/Moq.Tests/Async/AwaitableFixture.cs @@ -0,0 +1,23 @@ +// Copyright (c) 2007, Clarius Consulting, Manas Technology Solutions, InSTEDD, and Contributors. +// All rights reserved. Licensed under the BSD 3-Clause License; see License.txt. + +namespace Moq.Tests.Async +{ + using System.Threading.Tasks; + + using Moq.Async; + + using Xunit; + + public class AwaitableFixture + { + [Fact] + public void TryGetResultRecursive_is_recursive() + { + const int expectedResult = 42; + var obj = Task.FromResult(Task.FromResult(expectedResult)); + var result = Awaitable.TryGetResultRecursive(obj); + Assert.Equal(expectedResult, result); + } + } +} diff --git a/src/Moq.Tests/CSharpCompilerExpressionsFixture.cs b/src/Moq.Tests/CSharpCompilerExpressionsFixture.cs new file mode 100644 index 000000000..c7b0519a5 --- /dev/null +++ b/src/Moq.Tests/CSharpCompilerExpressionsFixture.cs @@ -0,0 +1,204 @@ +// Copyright (c) 2007, Clarius Consulting, Manas Technology Solutions, InSTEDD, and Contributors. +// All rights reserved. Licensed under the BSD 3-Clause License; see License.txt. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; + +using Xunit; + +namespace Moq.Tests +{ + public class CSharpCompilerExpressionsFixture + { + /// + /// Documents some cases that can lead to the C# compiler introducing / not introducing + /// `` nodes in s. These + /// tests are here to guide us when deciding whether we should add/remove such nodes. + /// + public class Convert_nodes + { + [Fact] + public void Null_literal_for_nullable_parameter() + { + AssertNoConvert(x => x.NullableInt(null)); + } + + [Fact] + public void Non_null_literal_value_for_nullable_parameter() + { + AssertConvert(x => x.NullableInt(0)); + } + + [Fact] + public void Null_value_in_nullable_variable_for_nullable_parameter() + { + int? arg = null; + AssertNoConvert(x => x.NullableInt(arg)); + } + + [Fact] + public void Non_nullable_variable_for_nullable_parameter() + { + int arg = 0; + AssertConvert(x => x.NullableInt(arg)); + } + + [Fact] + public void Non_null_literal_value_cast_to_nullable_type_for_nullable_parameter() + { + AssertConvert(x => x.NullableInt((int?)0)); + } + + [Fact] + public void Non_null_but_nullable_variable_for_nullable_parameter() + { + int? arg = 0; + AssertNoConvert(x => x.NullableInt(arg)); + } + + [Fact] + public void Boxing_of_literal_value() + { + AssertConvert(x => x.Object(0)); + } + + [Fact] + public void Boxing_of_variable() + { + int arg = 0; + AssertConvert(x => x.Object(arg)); + } + + [Fact] + public void Boxed_value() + { + object arg = 0; + AssertNoConvert(x => x.Object(arg)); + } + + [Fact] + public void Widening_of_literal_value_1() + { + AssertNoConvert(x => x.Long(0)); + } + + [Fact] + public void Widening_of_literal_value_2() + { + AssertNoConvert(x => x.Int((short)0)); + } + + [Fact] + public void Widening_of_variable_1() + { + int arg = 0; + AssertConvert(x => x.Long(arg)); + } + + [Fact] + public void Widening_of_variable_2() + { + short arg = 0; + AssertConvert(x => x.Int(arg)); + } + + [Fact] + public void Narrowing_of_literal_value() + { + AssertNoConvert(x => x.Short(0)); + } + + [Fact] + public void Narrowing_of_variable() + { + int arg = 0; + AssertConvert(x => x.Short(arg)); + } + + [Fact] + public void Downcast_of_interface_variable() + { + AssertNoConvert(x => x.Object(x)); + } + + public interface IX + { + void Int(int arg); + void Long(long arg); + void NullableInt(int? arg); + void Object(object arg); + void Short(long arg); + + /* Unmerged change from project 'Moq.Tests(net6.0)' + Before: + private static void AssertConvert(Expression> expression) + After: + static void AssertConvert(Expression> expression) + */ + } + + static void AssertConvert(Expression> expression) + { + var visitor = new FilteringVisitor(e => e.NodeType == ExpressionType.Convert); + visitor.Visit(expression.Body); + Assert.True(visitor.Result.Any()); + + /* Unmerged change from project 'Moq.Tests(net6.0)' + Before: + private static void AssertNoConvert(Expression> expression) + After: + static void AssertNoConvert(Expression> expression) + */ + } + + static void AssertNoConvert(Expression> expression) + { + var visitor = new FilteringVisitor(e => e.NodeType == ExpressionType.Convert); + visitor.Visit(expression.Body); + Assert.False(visitor.Result.Any()); + + /* Unmerged change from project 'Moq.Tests(net6.0)' + Before: + private sealed class FilteringVisitor : ExpressionVisitor + After: + sealed class FilteringVisitor : ExpressionVisitor + */ + } + } + + sealed class FilteringVisitor : ExpressionVisitor + + /* Unmerged change from project 'Moq.Tests(net6.0)' + Before: + private readonly Func predicate; + private readonly List result; + After: + readonly Func predicate; + readonly List result; + */ + { + readonly Func predicate; + readonly List result; + + public FilteringVisitor(Func predicate) + { + this.predicate = predicate; + this.result = new List(); + } + + public IReadOnlyList Result => this.result; + + public override Expression Visit(Expression node) + { + if (this.predicate(node)) + { + this.result.Add(node); + } + + return base.Visit(node); + } + } + } +} diff --git a/tests/Moq.Tests/CallBaseDefaultInterfaceImplementationsFixture.cs b/src/Moq.Tests/CallBaseDefaultInterfaceImplementationsFixture.cs similarity index 100% rename from tests/Moq.Tests/CallBaseDefaultInterfaceImplementationsFixture.cs rename to src/Moq.Tests/CallBaseDefaultInterfaceImplementationsFixture.cs diff --git a/src/Moq.Tests/CallBaseFixture.cs b/src/Moq.Tests/CallBaseFixture.cs new file mode 100644 index 000000000..5c2b32836 --- /dev/null +++ b/src/Moq.Tests/CallBaseFixture.cs @@ -0,0 +1,51 @@ +// Copyright (c) 2007, Clarius Consulting, Manas Technology Solutions, InSTEDD, and Contributors. +// All rights reserved. Licensed under the BSD 3-Clause License; see License.txt. + +using Xunit; + +namespace Moq.Tests +{ + public class CallBaseFixture + { + [Fact] + public void Void_method_in_base_class_not_called_when_CallBase_not_specified() + { + var mock = new Mock(); + + mock.Object.Method(); + + Assert.False(mock.Object.MethodInvoked); + } + + [Fact] + public void Void_method_in_base_class_called_when_CallBase_specified_on_mock() + { + var mock = new Mock() { CallBase = true }; + + mock.Object.Method(); + + Assert.True(mock.Object.MethodInvoked); + } + + [Fact] + public void Void_method_in_base_class_called_when_CallBase_specified_on_setup() + { + var mock = new Mock(); + mock.Setup(m => m.Method()).CallBase(); + + mock.Object.Method(); + + Assert.True(mock.Object.MethodInvoked); + } + + public class Base + { + public bool MethodInvoked { get; private set; } + + public virtual void Method() + { + this.MethodInvoked = true; + } + } + } +} diff --git a/src/Moq.Tests/CallbackDelegateValidationFixture.cs b/src/Moq.Tests/CallbackDelegateValidationFixture.cs new file mode 100644 index 000000000..9d286452b --- /dev/null +++ b/src/Moq.Tests/CallbackDelegateValidationFixture.cs @@ -0,0 +1,116 @@ +// Copyright (c) 2007, Clarius Consulting, Manas Technology Solutions, InSTEDD, and Contributors. +// All rights reserved. Licensed under the BSD 3-Clause License; see License.txt. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; + +using Moq.Language.Flow; + +using Xunit; + +namespace Moq.Tests +{ + /// + /// This fixture targets the `Callback` delegate validation logic. + /// + /// + public class CallbackDelegateValidationFixture + + /* Unmerged change from project 'Moq.Tests(net6.0)' + Before: + private ISetup setup; + After: + ISetup setup; + */ + { + ISetup setup; + + public CallbackDelegateValidationFixture() + { + var mock = new Mock(); + this.setup = mock.Setup(m => m.Action(It.IsAny())); + } + + // Nothing surprising here. + [Fact] + public void Callback_accepts_instance_method_as_callback() + { + var instance = new Instance(); + Action callback = instance.Action; + Assert.Single(callback.Method.GetParameters()); + Assert.Same(instance, callback.Target); + + this.setup.Callback(callback); + } + + // Nothing surprising here. + [Fact] + public void Callback_accepts_static_method_as_callback() + { + Action callback = Static.Action; + Assert.Single(callback.Method.GetParameters()); + Assert.Null(callback.Target); + + this.setup.Callback(callback); + } + + // This may seem surprising because the extension method has a different number + // of declared parameters (2) than the method being set up (1). Since C# supports + // this syntax just fine (proof below), Moq should accept this too. The reason why + // this works is because the first (`this`) parameter is bound to an object. + [Fact] + public void Callback_accepts_bound_extension_method_as_callback_despite_additional_parameter() + { + var instance = Enumerable.Range(1, 10); + Action callback = instance.Action; + Assert.Equal(2, callback.Method.GetParameters().Length); + Assert.Same(instance, callback.Target); + + this.setup.Callback(callback); + } + + // This doesn't look suspicious at all, but it is very similar to the above test. + // Methods that result from compiling a LINQ expression tree have an additional + // (bound) first parameter of type `System.Runtime.CompilerServices.Closure`. + // That is, they have a different parameter count than the method being set up, + // but Moq should still accept it. + [Fact] + public void Callback_accepts_compiled_method_as_callback_despite_additional_parameter() + { + Expression> callbackExpr = x => Console.WriteLine(); + Action callback = callbackExpr.Compile(); + Assert.Equal(2, callback.Method.GetParameters().Length); + Assert.NotNull(callback.Target); + + this.setup.Callback(callback); + } + + public interface IFoo + { + void Action(int x); + } + } + + public partial class Instance + { + public void Action(int x) + { + } + } + + public static partial class Static + { + public static void Action(int x) + { + } + } + + public static partial class Extension + { + public static void Action(this IEnumerable self, int x) + { + } + } +} diff --git a/src/Moq.Tests/CallbacksFixture.cs b/src/Moq.Tests/CallbacksFixture.cs new file mode 100644 index 000000000..8c130842a --- /dev/null +++ b/src/Moq.Tests/CallbacksFixture.cs @@ -0,0 +1,597 @@ +// Copyright (c) 2007, Clarius Consulting, Manas Technology Solutions, InSTEDD, and Contributors. +// All rights reserved. Licensed under the BSD 3-Clause License; see License.txt. + +using System; + +using Xunit; + +namespace Moq.Tests +{ + public class CallbacksFixture + { + [Fact] + public void ExecutesCallbackWhenVoidMethodIsCalled() + { + var mock = new Mock(); + bool called = false; + mock.Setup(x => x.Submit()).Callback(() => called = true); + + mock.Object.Submit(); + Assert.True(called); + } + + [Fact] + public void ExecutesCallbackWhenNonVoidMethodIsCalled() + { + var mock = new Mock(); + bool called = false; + mock.Setup(x => x.Execute("ping")).Callback(() => called = true).Returns("ack"); + + Assert.Equal("ack", mock.Object.Execute("ping")); + Assert.True(called); + } + + [Fact] + public void CallbackCalledWithoutArgumentsForMethodCallWithArguments() + { + var mock = new Mock(); + bool called = false; + mock.Setup(x => x.Submit(It.IsAny())).Callback(() => called = true); + + mock.Object.Submit("blah"); + Assert.True(called); + } + + [Fact] + public void FriendlyErrorWhenCallbackArgumentCountNotMatch() + { + var mock = new Mock(); + + Assert.Throws(() => + mock.Setup(x => x.Submit(It.IsAny())) + .Callback((string s1, string s2) => System.Console.WriteLine(s1 + s2))); + } + + [Fact] + public void CallbackCalledWithOneArgument() + { + var mock = new Mock(); + string callbackArg = null; + mock.Setup(x => x.Submit(It.IsAny())).Callback((string s) => callbackArg = s); + + mock.Object.Submit("blah"); + Assert.Equal("blah", callbackArg); + } + + [Fact] + public void CallbackCalledWithTwoArguments() + { + var mock = new Mock(); + string callbackArg1 = null; + string callbackArg2 = null; + mock.Setup(x => x.Submit(It.IsAny(), It.IsAny())) + .Callback((string s1, string s2) => { callbackArg1 = s1; callbackArg2 = s2; }); + + mock.Object.Submit("blah1", "blah2"); + Assert.Equal("blah1", callbackArg1); + Assert.Equal("blah2", callbackArg2); + } + + [Fact] + public void CallbackCalledWithThreeArguments() + { + var mock = new Mock(); + string callbackArg1 = null; + string callbackArg2 = null; + string callbackArg3 = null; + mock.Setup(x => x.Submit(It.IsAny(), It.IsAny(), It.IsAny())) + .Callback((string s1, string s2, string s3) => { callbackArg1 = s1; callbackArg2 = s2; callbackArg3 = s3; }); + + mock.Object.Submit("blah1", "blah2", "blah3"); + Assert.Equal("blah1", callbackArg1); + Assert.Equal("blah2", callbackArg2); + Assert.Equal("blah3", callbackArg3); + } + + [Fact] + public void CallbackCalledWithFourArguments() + { + var mock = new Mock(); + string callbackArg1 = null; + string callbackArg2 = null; + string callbackArg3 = null; + string callbackArg4 = null; + mock.Setup(x => x.Submit(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .Callback((string s1, string s2, string s3, string s4) => { callbackArg1 = s1; callbackArg2 = s2; callbackArg3 = s3; callbackArg4 = s4; }); + + mock.Object.Submit("blah1", "blah2", "blah3", "blah4"); + Assert.Equal("blah1", callbackArg1); + Assert.Equal("blah2", callbackArg2); + Assert.Equal("blah3", callbackArg3); + Assert.Equal("blah4", callbackArg4); + } + + [Fact] + public void CallbackCalledWithFiveArguments() + { + var mock = new Mock(); + string callbackArg1 = null; + string callbackArg2 = null; + string callbackArg3 = null; + string callbackArg4 = null; + string callbackArg5 = null; + mock.Setup(x => x.Submit(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .Callback((string s1, string s2, string s3, string s4, string s5) => { callbackArg1 = s1; callbackArg2 = s2; callbackArg3 = s3; callbackArg4 = s4; callbackArg5 = s5; }); + + mock.Object.Submit("blah1", "blah2", "blah3", "blah4", "blah5"); + Assert.Equal("blah1", callbackArg1); + Assert.Equal("blah2", callbackArg2); + Assert.Equal("blah3", callbackArg3); + Assert.Equal("blah4", callbackArg4); + Assert.Equal("blah5", callbackArg5); + } + + [Fact] + public void CallbackCalledWithSixArguments() + { + var mock = new Mock(); + string callbackArg1 = null; + string callbackArg2 = null; + string callbackArg3 = null; + string callbackArg4 = null; + string callbackArg5 = null; + string callbackArg6 = null; + mock.Setup(x => x.Submit(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .Callback((string s1, string s2, string s3, string s4, string s5, string s6) => { callbackArg1 = s1; callbackArg2 = s2; callbackArg3 = s3; callbackArg4 = s4; callbackArg5 = s5; callbackArg6 = s6; }); + + mock.Object.Submit("blah1", "blah2", "blah3", "blah4", "blah5", "blah6"); + Assert.Equal("blah1", callbackArg1); + Assert.Equal("blah2", callbackArg2); + Assert.Equal("blah3", callbackArg3); + Assert.Equal("blah4", callbackArg4); + Assert.Equal("blah5", callbackArg5); + Assert.Equal("blah6", callbackArg6); + } + + [Fact] + public void CallbackCalledWithSevenArguments() + { + var mock = new Mock(); + string callbackArg1 = null; + string callbackArg2 = null; + string callbackArg3 = null; + string callbackArg4 = null; + string callbackArg5 = null; + string callbackArg6 = null; + string callbackArg7 = null; + mock.Setup(x => x.Submit(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .Callback((string s1, string s2, string s3, string s4, string s5, string s6, string s7) => { callbackArg1 = s1; callbackArg2 = s2; callbackArg3 = s3; callbackArg4 = s4; callbackArg5 = s5; callbackArg6 = s6; callbackArg7 = s7; }); + + mock.Object.Submit("blah1", "blah2", "blah3", "blah4", "blah5", "blah6", "blah7"); + Assert.Equal("blah1", callbackArg1); + Assert.Equal("blah2", callbackArg2); + Assert.Equal("blah3", callbackArg3); + Assert.Equal("blah4", callbackArg4); + Assert.Equal("blah5", callbackArg5); + Assert.Equal("blah6", callbackArg6); + Assert.Equal("blah7", callbackArg7); + } + + [Fact] + public void CallbackCalledWithEightArguments() + { + var mock = new Mock(); + string callbackArg1 = null; + string callbackArg2 = null; + string callbackArg3 = null; + string callbackArg4 = null; + string callbackArg5 = null; + string callbackArg6 = null; + string callbackArg7 = null; + string callbackArg8 = null; + mock.Setup(x => x.Submit(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .Callback((string s1, string s2, string s3, string s4, string s5, string s6, string s7, string s8) => { callbackArg1 = s1; callbackArg2 = s2; callbackArg3 = s3; callbackArg4 = s4; callbackArg5 = s5; callbackArg6 = s6; callbackArg7 = s7; callbackArg8 = s8; }); + + mock.Object.Submit("blah1", "blah2", "blah3", "blah4", "blah5", "blah6", "blah7", "blah8"); + Assert.Equal("blah1", callbackArg1); + Assert.Equal("blah2", callbackArg2); + Assert.Equal("blah3", callbackArg3); + Assert.Equal("blah4", callbackArg4); + Assert.Equal("blah5", callbackArg5); + Assert.Equal("blah6", callbackArg6); + Assert.Equal("blah7", callbackArg7); + Assert.Equal("blah8", callbackArg8); + } + + [Fact] + public void CallbackCalledWithOneArgumentForNonVoidMethod() + { + var mock = new Mock(); + string callbackArg1 = null; + mock.Setup(x => x.Execute(It.IsAny())) + .Callback((string s1) => callbackArg1 = s1) + .Returns("foo"); + + mock.Object.Execute("blah1"); + Assert.Equal("blah1", callbackArg1); + } + + [Fact] + public void CallbackCalledWithTwoArgumentsForNonVoidMethod() + { + var mock = new Mock(); + string callbackArg1 = null; + string callbackArg2 = null; + mock.Setup(x => x.Execute(It.IsAny(), It.IsAny())) + .Callback((string s1, string s2) => { callbackArg1 = s1; callbackArg2 = s2; }) + .Returns("foo"); + + mock.Object.Execute("blah1", "blah2"); + Assert.Equal("blah1", callbackArg1); + Assert.Equal("blah2", callbackArg2); + } + + [Fact] + public void CallbackCalledWithThreeArgumentsForNonVoidMethod() + { + var mock = new Mock(); + string callbackArg1 = null; + string callbackArg2 = null; + string callbackArg3 = null; + mock.Setup(x => x.Execute(It.IsAny(), It.IsAny(), It.IsAny())) + .Callback((string s1, string s2, string s3) => { callbackArg1 = s1; callbackArg2 = s2; callbackArg3 = s3; }) + .Returns("foo"); + + mock.Object.Execute("blah1", "blah2", "blah3"); + Assert.Equal("blah1", callbackArg1); + Assert.Equal("blah2", callbackArg2); + Assert.Equal("blah3", callbackArg3); + } + + [Fact] + public void CallbackCalledWithFourArgumentsForNonVoidMethod() + { + var mock = new Mock(); + string callbackArg1 = null; + string callbackArg2 = null; + string callbackArg3 = null; + string callbackArg4 = null; + mock.Setup(x => x.Execute(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .Callback((string s1, string s2, string s3, string s4) => { callbackArg1 = s1; callbackArg2 = s2; callbackArg3 = s3; callbackArg4 = s4; }) + .Returns("foo"); + + mock.Object.Execute("blah1", "blah2", "blah3", "blah4"); + Assert.Equal("blah1", callbackArg1); + Assert.Equal("blah2", callbackArg2); + Assert.Equal("blah3", callbackArg3); + Assert.Equal("blah4", callbackArg4); + } + + [Fact] + public void CallbackCalledWithFiveArgumentsForNonVoidMethod() + { + var mock = new Mock(); + string callbackArg1 = null; + string callbackArg2 = null; + string callbackArg3 = null; + string callbackArg4 = null; + string callbackArg5 = null; + mock.Setup(x => x.Execute(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .Callback((string s1, string s2, string s3, string s4, string s5) => { callbackArg1 = s1; callbackArg2 = s2; callbackArg3 = s3; callbackArg4 = s4; callbackArg5 = s5; }) + .Returns("foo"); + + mock.Object.Execute("blah1", "blah2", "blah3", "blah4", "blah5"); + Assert.Equal("blah1", callbackArg1); + Assert.Equal("blah2", callbackArg2); + Assert.Equal("blah3", callbackArg3); + Assert.Equal("blah4", callbackArg4); + Assert.Equal("blah5", callbackArg5); + } + + [Fact] + public void CallbackCalledWithSixArgumentsForNonVoidMethod() + { + var mock = new Mock(); + string callbackArg1 = null; + string callbackArg2 = null; + string callbackArg3 = null; + string callbackArg4 = null; + string callbackArg5 = null; + string callbackArg6 = null; + mock.Setup(x => x.Execute(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .Callback((string s1, string s2, string s3, string s4, string s5, string s6) => { callbackArg1 = s1; callbackArg2 = s2; callbackArg3 = s3; callbackArg4 = s4; callbackArg5 = s5; callbackArg6 = s6; }) + .Returns("foo"); + + mock.Object.Execute("blah1", "blah2", "blah3", "blah4", "blah5", "blah6"); + Assert.Equal("blah1", callbackArg1); + Assert.Equal("blah2", callbackArg2); + Assert.Equal("blah3", callbackArg3); + Assert.Equal("blah4", callbackArg4); + Assert.Equal("blah5", callbackArg5); + Assert.Equal("blah6", callbackArg6); + } + + [Fact] + public void CallbackCalledWithSevenArgumentsForNonVoidMethod() + { + var mock = new Mock(); + string callbackArg1 = null; + string callbackArg2 = null; + string callbackArg3 = null; + string callbackArg4 = null; + string callbackArg5 = null; + string callbackArg6 = null; + string callbackArg7 = null; + mock.Setup(x => x.Execute(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .Callback((string s1, string s2, string s3, string s4, string s5, string s6, string s7) => { callbackArg1 = s1; callbackArg2 = s2; callbackArg3 = s3; callbackArg4 = s4; callbackArg5 = s5; callbackArg6 = s6; callbackArg7 = s7; }) + .Returns("foo"); + + mock.Object.Execute("blah1", "blah2", "blah3", "blah4", "blah5", "blah6", "blah7"); + Assert.Equal("blah1", callbackArg1); + Assert.Equal("blah2", callbackArg2); + Assert.Equal("blah3", callbackArg3); + Assert.Equal("blah4", callbackArg4); + Assert.Equal("blah5", callbackArg5); + Assert.Equal("blah6", callbackArg6); + Assert.Equal("blah7", callbackArg7); + } + + [Fact] + public void CallbackCalledWithEightArgumentsForNonVoidMethod() + { + var mock = new Mock(); + string callbackArg1 = null; + string callbackArg2 = null; + string callbackArg3 = null; + string callbackArg4 = null; + string callbackArg5 = null; + string callbackArg6 = null; + string callbackArg7 = null; + string callbackArg8 = null; + mock.Setup(x => x.Execute(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .Callback((string s1, string s2, string s3, string s4, string s5, string s6, string s7, string s8) => { callbackArg1 = s1; callbackArg2 = s2; callbackArg3 = s3; callbackArg4 = s4; callbackArg5 = s5; callbackArg6 = s6; callbackArg7 = s7; callbackArg8 = s8; }) + .Returns("foo"); + + mock.Object.Execute("blah1", "blah2", "blah3", "blah4", "blah5", "blah6", "blah7", "blah8"); + Assert.Equal("blah1", callbackArg1); + Assert.Equal("blah2", callbackArg2); + Assert.Equal("blah3", callbackArg3); + Assert.Equal("blah4", callbackArg4); + Assert.Equal("blah5", callbackArg5); + Assert.Equal("blah6", callbackArg6); + Assert.Equal("blah7", callbackArg7); + Assert.Equal("blah8", callbackArg8); + } + + [Fact] + public void CallbackCalledAfterReturnsCall() + { + var mock = new Mock(); + bool returnsCalled = false; + bool beforeCalled = false; + bool afterCalled = false; + + mock.Setup(foo => foo.Execute("ping")) + .Callback(() => { Assert.False(returnsCalled); beforeCalled = true; }) + .Returns(() => { returnsCalled = true; return "ack"; }) + .Callback(() => { Assert.True(returnsCalled); afterCalled = true; }); + + Assert.Equal("ack", mock.Object.Execute("ping")); + + Assert.True(beforeCalled); + Assert.True(afterCalled); + } + + [Fact] + public void CallbackCalledAfterReturnsCallWithArg() + { + var mock = new Mock(); + bool returnsCalled = false; + + mock.Setup(foo => foo.Execute(It.IsAny())) + .Callback(s => Assert.False(returnsCalled)) + .Returns(() => { returnsCalled = true; return "ack"; }) + .Callback(s => Assert.True(returnsCalled)); + + mock.Object.Execute("ping"); + + Assert.True(returnsCalled); + } + + [Fact] + public void CallbackCanReceiveABaseClass() + { + var mock = new Mock(MockBehavior.Strict); + mock.Setup(foo => foo.Method(It.IsAny())).Callback(TraceMe); + + mock.Object.Method(new Derived()); + } + + [Fact] + public void CallbackCanBeImplementedByExtensionMethod() + { + var mock = new Mock(); + string receivedArgument = null; + Action innerCallback = param => { receivedArgument = param; }; + + // Delegate parameter currying can confuse Moq (used by extension delegates) + Action callback = innerCallback.ExtensionCallbackHelper; + mock.Setup(x => x.Submit(It.IsAny())).Callback(callback); + + mock.Object.Submit("blah"); + Assert.Equal("blah extended", receivedArgument); + } + + [Fact] + public void CallbackWithRefParameterReceivesArguments() + { + var input = "input"; + var received = default(string); + + var mock = new Mock(); + mock.Setup(f => f.Execute(ref input)) + .Callback(new ExecuteRHandler((ref string arg1) => + { + received = arg1; + })); + + mock.Object.Execute(ref input); + Assert.Equal("input", input); + Assert.Equal(input, received); + } + + [Fact] + public void CallbackWithRefParameterCanModifyRefParameter() + { + var value = "input"; + + var mock = new Mock(); + mock.Setup(f => f.Execute(ref value)) + .Callback(new ExecuteRHandler((ref string arg1) => + { + arg1 = "output"; + })); + + Assert.Equal("input", value); + mock.Object.Execute(ref value); + Assert.Equal("output", value); + } + + [Fact] + public void CallbackWithRefParameterCannotModifyNonRefParameter() + { + var _ = default(string); + var value = "input"; + + var mock = new Mock(); + mock.Setup(f => f.Execute(ref _, value)) + .Callback(new ExecuteRVHandler((ref string arg1, string arg2) => + { + arg2 = "output"; + })); + + Assert.Equal("input", value); + mock.Object.Execute(ref _, value); + Assert.Equal("input", value); + } + + [Fact] + public void CallbackWithIndexerSetter() + { + int x = default(int); + int result = default(int); + + var mock = new Mock(); + mock.SetupSet(f => f[10] = It.IsAny()) + .Callback(new Action((x_, result_) => + { + x = x_; + result = result_; + })); + + mock.Object[10] = 5; + Assert.Equal(10, x); + Assert.Equal(5, result); + } + + [Fact] + public void CallbackWithMultipleArgumentIndexerSetterWithoutAny() + { + int x = default(int); + int y = default(int); + int result = default(int); + + var mock = new Mock(); + mock.SetupSet(f => f[3, 13] = It.IsAny()) + .Callback(new Action((x_, y_, result_) => + { + x = x_; + y = y_; + result = result_; + })); + + mock.Object[3, 13] = 2; + Assert.Equal(3, x); + Assert.Equal(13, y); + Assert.Equal(2, result); + } + + [Fact] + public void Type_of_exception_thrown_from_InvocationFunc_callback_should_be_preserved() + { + var mock = new Mock(); + mock.Setup(m => m.Submit("good", "bad")).Returns(new InvocationFunc(invocation => + { + throw new Exception("very bad"); // this used to be erroneously wrapped as a `TargetInvocationException` + })); + + var ex = Assert.Throws(() => mock.Object.Submit("good", "bad")); + Assert.Equal("very bad", ex.Message); + } + + public interface IInterface + { + void Method(Derived b); + } + + public class Base + { + } + + public class Derived : Base + + /* Unmerged change from project 'Moq.Tests(net6.0)' + Before: + private void TraceMe(Base b) + After: + void TraceMe(Base b) + */ + { + } + + void TraceMe(Base b) + { + } + + public interface IFoo + { + void Submit(); + void Submit(string command); + string Submit(string arg1, string arg2); + string Submit(string arg1, string arg2, string arg3); + string Submit(string arg1, string arg2, string arg3, string arg4); + void Submit(string arg1, string arg2, string arg3, string arg4, string arg5); + void Submit(string arg1, string arg2, string arg3, string arg4, string arg5, string arg6); + void Submit(string arg1, string arg2, string arg3, string arg4, string arg5, string arg6, string arg7); + void Submit(string arg1, string arg2, string arg3, string arg4, string arg5, string arg6, string arg7, string arg8); + string Execute(string command); + string Execute(string arg1, string arg2); + string Execute(string arg1, string arg2, string arg3); + string Execute(string arg1, string arg2, string arg3, string arg4); + string Execute(string arg1, string arg2, string arg3, string arg4, string arg5); + string Execute(string arg1, string arg2, string arg3, string arg4, string arg5, string arg6); + string Execute(string arg1, string arg2, string arg3, string arg4, string arg5, string arg6, string arg7); + string Execute(string arg1, string arg2, string arg3, string arg4, string arg5, string arg6, string arg7, string arg8); + + string Execute(ref string arg1); + string Execute(ref string arg1, string arg2); + + int Value { get; set; } + + int this[int x] { get; set; } + + int this[int x, int y] { get; set; } + } + + public delegate void ExecuteRHandler(ref string arg1); + public delegate void ExecuteRVHandler(ref string arg1, string arg2); + } + + static class Extensions + { + public static void ExtensionCallbackHelper(this Action inner, string param) + { + inner.Invoke(param + " extended"); + } + } +} diff --git a/src/Moq.Tests/CaptureFixture.cs b/src/Moq.Tests/CaptureFixture.cs new file mode 100644 index 000000000..1639febd5 --- /dev/null +++ b/src/Moq.Tests/CaptureFixture.cs @@ -0,0 +1,100 @@ +// Copyright (c) 2007, Clarius Consulting, Manas Technology Solutions, InSTEDD, and Contributors. +// All rights reserved. Licensed under the BSD 3-Clause License; see License.txt. + +using System.Collections.Generic; + +using Xunit; + +namespace Moq.Tests +{ + public class CaptureFixture + { + [Fact] + public void CanCaptureAnyParameterInCollection() + { + var items = new List(); + var mock = new Mock(); + mock.Setup(x => x.DoSomething(Capture.In(items))); + + mock.Object.DoSomething("Hello!"); + + var expectedValues = new List { "Hello!" }; + Assert.Equal(expectedValues, items); + } + + [Fact] + public void ShouldOnlyCaptureParameterForSpecificArgumentBeforeCollection() + { + var items = new List(); + var mock = new Mock(); + mock.Setup(x => x.DoSomething(1, Capture.In(items))); + + mock.Object.DoSomething(1, "Hello!"); + mock.Object.DoSomething(2, "World"); + + var expectedValues = new List { "Hello!" }; + Assert.Equal(expectedValues, items); + } + + [Fact] + public void ShouldOnlyCaptureParameterForSpecificArgumentAfterCollection() + { + var items = new List(); + var mock = new Mock(); + mock.Setup(x => x.DoSomething(Capture.In(items), 1)); + + mock.Object.DoSomething("Hello!", 1); + mock.Object.DoSomething("World", 2); + + var expectedValues = new List { "Hello!" }; + Assert.Equal(expectedValues, items); + } + + [Fact] + public void CanCaptureSpecificParameterInCollection() + { + var items = new List(); + var mock = new Mock(); + mock.Setup(x => x.DoSomething(Capture.In(items, p => p.StartsWith("W")))); + + mock.Object.DoSomething("Hello!"); + mock.Object.DoSomething("World!"); + + var expectedValues = new List { "World!" }; + Assert.Equal(expectedValues, items); + } + + [Fact] + public void ShouldNotCaptureParameterWhenConditionalSetupIsFalse() + { + var captures = new List(); + var mock = new Mock(); + mock.When(() => false).Setup(m => m.DoSomething(Capture.In(captures))); + + mock.Object.DoSomething("X"); + + Assert.Empty(captures); + } + + [Fact] + public void Can_be_used_with_Verify_to_replay_arguments() + { + var mock = new Mock(); + mock.Object.DoSomething("1"); + mock.Object.DoSomething("2"); + mock.Object.DoSomething("3"); + + var args = new List(); + mock.Verify(m => m.DoSomething(Capture.In(args)), Times.Exactly(3)); + + Assert.Equal(new[] { "1", "2", "3" }, args); + } + + public interface IFoo + { + void DoSomething(string s); + void DoSomething(int i, string s); + void DoSomething(string s, int i); + } + } +} diff --git a/src/Moq.Tests/CaptureMatchFixture.cs b/src/Moq.Tests/CaptureMatchFixture.cs new file mode 100644 index 000000000..9c118a07c --- /dev/null +++ b/src/Moq.Tests/CaptureMatchFixture.cs @@ -0,0 +1,44 @@ +// Copyright (c) 2007, Clarius Consulting, Manas Technology Solutions, InSTEDD, and Contributors. +// All rights reserved. Licensed under the BSD 3-Clause License; see License.txt. + +using Xunit; + +namespace Moq.Tests +{ + public class CaptureMatchFixture + { + [Fact] + public void CanRunCaptureCallback() + { + var capturedValue = string.Empty; + var captureMatch = new CaptureMatch(s => capturedValue = s); + + var mock = new Mock(); + mock.Setup(x => x.DoSomething(Capture.With(captureMatch))); + + mock.Object.DoSomething("Hello!"); + + Assert.Equal("Hello!", capturedValue); + } + + [Fact] + public void CanRunCaptureCallbackWithPredicate() + { + var capturedValue = string.Empty; + var captureMatch = new CaptureMatch(s => capturedValue += s, s => s.StartsWith("W")); + + var mock = new Mock(); + mock.Setup(x => x.DoSomething(Capture.With(captureMatch))); + + mock.Object.DoSomething("Hello!"); + mock.Object.DoSomething("World!"); + + Assert.Equal("World!", capturedValue); + } + + public interface IFoo + { + void DoSomething(string item); + } + } +} diff --git a/src/Moq.Tests/ComCompatibilityFixture.cs b/src/Moq.Tests/ComCompatibilityFixture.cs new file mode 100644 index 000000000..1e2ca9ffa --- /dev/null +++ b/src/Moq.Tests/ComCompatibilityFixture.cs @@ -0,0 +1,152 @@ +// Copyright (c) 2007, Clarius Consulting, Manas Technology Solutions, InSTEDD, and Contributors. +// All rights reserved. Licensed under the BSD 3-Clause License; see License.txt. + +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +using Moq.Tests.ComTypes; + +using Xunit; + +namespace Moq.Tests +{ + public class ComCompatibilityFixture + { + [Fact] + public void COM_interop_type_event_has_accessors_that_are_not_marked_as_specialname() + { + // If this test starts failing, we need to review our extension methods that check + // whether a method is an event `add` or `remove` accessor. It might mean we can + // do a quick test for `method.IsSpecialName` before performing more costly checks. + + var @event = typeof(IButtonEvents_Event).GetEvent(nameof(IButtonEvents_Event.Click)); + Assert.All(@event.GetAccessors(), accessor => Assert.False(accessor.IsSpecialName, "Accessor is marked as `specialname`.")); + } + + // The following two tests verify whether Moq can deal with events that are defined in + // a COM interop type. This test is relevant because the Type Library Importer (`tlbimp`) + // and the CLR runtime perform some magic to make COM connection points look like .NET + // events. Notably, the generated .NET event accessor methods (`add_X` and `remove_X`) + // are not marked with the `specialname` IL flag as is usual for .NET event accessors. + + [Fact] + public void Can_subscribe_to_and_raise_COM_interop_type_event() + { + var mock = new Mock(); + var eventRaiseCount = 0; + IButtonEvents_ClickEventHandler handler = () => ++eventRaiseCount; + + mock.Object.Click += handler; + mock.Raise(x => x.Click += null); + + Assert.Equal(1, eventRaiseCount); + } + + [Fact] + public void Can_unsubscribe_from_COM_interop_type_event() + { + var mock = new Mock(); + var eventRaiseCount = 0; + IButtonEvents_ClickEventHandler handler = () => ++eventRaiseCount; + + mock.Object.Click += handler; + mock.Raise(x => x.Click += null); + + mock.Object.Click -= handler; + mock.Raise(x => x.Click += null); + + Assert.Equal(1, eventRaiseCount); + } + + [Fact] + public void COM_interop_type_property_has_accessors_that_are_marked_as_specialname() + { + var property = typeof(IHasProperty).GetProperty(nameof(IHasProperty.Property)); + Assert.All(property.GetAccessors(), accessor => Assert.True(accessor.IsSpecialName, "Accessor is not marked as `specialname`.")); + } + + [Fact] + public void COM_interop_type_property_getter_is_recognized_as_such() + { + var property = typeof(IHasProperty).GetProperty(nameof(IHasProperty.Property)); + var getter = property.GetGetMethod(true); + Assert.True(getter.IsGetAccessor()); + } + + [Fact] + public void COM_interop_type_property_setter_is_recognized_as_such() + { + var property = typeof(IHasProperty).GetProperty(nameof(IHasProperty.Property)); + var setter = property.GetSetMethod(true); + Assert.True(setter.IsSetAccessor()); + } + + [Fact] + public void COM_interop_type_indexer_has_accessors_that_are_marked_as_specialname() + { + var indexer = typeof(IHasIndexer).GetProperty("Item"); + Assert.All(indexer.GetAccessors(), accessor => Assert.True(accessor.IsSpecialName, "Accessor is not marked as `specialname`.")); + } + + [Fact] + public void COM_interop_type_indexer_getter_is_recognized_as_such() + { + var indexer = typeof(IHasIndexer).GetProperty("Item"); + var getter = indexer.GetGetMethod(true); + Assert.True(getter.IsGetAccessor() && getter.IsIndexerAccessor()); + } + + [Fact] + public void COM_interop_type_indexer_setter_is_recognized_as_such() + { + var indexer = typeof(IHasIndexer).GetProperty("Item"); + var setter = indexer.GetSetMethod(true); + Assert.True(setter.IsSetAccessor() && setter.IsIndexerAccessor()); + } + + [Fact] + public void Can_create_mock_of_Excel_Workbook_using_Mock_Of_1() + { + _ = Mock.Of(workbook => workbook.FullName == ""); + } + + [Fact] + public void Can_create_mock_of_Excel_Workbook_using_Mock_Of_2() + { + _ = Mock.Of(workbook => workbook.FullName == ""); + } + + // The following two interfaces are simplified versions of the `_Workbook` interface from + // two different versions of the `Microsoft.Office.Excel.Interop` interop assemblies. + // Note how they differ only in one `[CompilerGenerated]` attribute. + + [ComImport] + [Guid("000208DA-0000-0000-C000-000000000046")] + public interface GoodWorkbook + { + [DispId(289)] + string FullName + { + [DispId(289)] + [LCIDConversion(0)] + [return: MarshalAs(UnmanagedType.BStr)] + get; + } + } + + [ComImport] + [CompilerGenerated] + [Guid("000208DA-0000-0000-C000-000000000046")] + public interface BadWorkbook + { + [DispId(289)] + string FullName + { + [DispId(289)] + [LCIDConversion(0)] + [return: MarshalAs(UnmanagedType.BStr)] + get; + } + } + } +} diff --git a/src/Moq.Tests/ConditionalSetupFixture.cs b/src/Moq.Tests/ConditionalSetupFixture.cs new file mode 100644 index 000000000..3216b7165 --- /dev/null +++ b/src/Moq.Tests/ConditionalSetupFixture.cs @@ -0,0 +1,167 @@ +// Copyright (c) 2007, Clarius Consulting, Manas Technology Solutions, InSTEDD, and Contributors. +// All rights reserved. Licensed under the BSD 3-Clause License; see License.txt. + +using Xunit; + +namespace Moq.Tests +{ + public class ConditionalSetupFixture + { + [Fact] + public void ChooseAffirmativeExpectationOnMethod() + { + var mock = new Mock(); + + var when = true; + + mock.When(() => when).Setup(x => x.Foo()).Returns("bar"); + mock.When(() => !when).Setup(x => x.Foo()).Returns("no bar"); + + Assert.Equal("bar", mock.Object.Foo()); + + when = false; + Assert.Equal("no bar", mock.Object.Foo()); + + when = true; + Assert.Equal("bar", mock.Object.Foo()); + } + + [Fact] + public void ChooseAffirmativeExpetationOnVoidMethod() + { + var mock = new Mock(); + + var when = true; + var positive = false; + var negative = false; + + mock.When(() => when).Setup(x => x.Bar()).Callback(() => positive = true); + mock.When(() => !when).Setup(x => x.Bar()).Callback(() => negative = true); + + mock.Object.Bar(); + + Assert.True(positive); + Assert.False(negative); + + when = false; + positive = false; + mock.Object.Bar(); + + Assert.False(positive); + Assert.True(negative); + + when = true; + negative = false; + mock.Object.Bar(); + + Assert.True(positive); + Assert.False(negative); + } + + [Fact] + public void ChooseAffirmativeExpectationOnPropertyGetter() + { + var mock = new Mock(); + + var first = true; + + mock.When(() => first).SetupGet(x => x.Value).Returns("bar"); + mock.When(() => !first).SetupGet(x => x.Value).Returns("no bar"); + + Assert.Equal("bar", mock.Object.Value); + first = false; + Assert.Equal("no bar", mock.Object.Value); + first = true; + Assert.Equal("bar", mock.Object.Value); + } + + [Fact] + public void ChooseAffirmativeExpetationOnPropertySetter() + { + var mock = new Mock(); + + var when = true; + var positive = false; + var negative = false; + + mock.When(() => when).SetupSet(x => x.Value = "foo").Callback(() => positive = true); + mock.When(() => !when).SetupSet(x => x.Value = "foo").Callback(() => negative = true); + + mock.Object.Value = "foo"; + + Assert.True(positive); + Assert.False(negative); + + when = false; + positive = false; + mock.Object.Value = "foo"; + + Assert.False(positive); + Assert.True(negative); + + when = true; + negative = false; + mock.Object.Value = "foo"; + + Assert.True(positive); + Assert.False(negative); + } + + [Fact] + public void ChooseAffirmativeExpetationOnTypedPropertySetter() + { + var mock = new Mock(); + + var when = true; + var positive = false; + var negative = false; + + mock.When(() => when).SetupSet(x => x.Value = "foo").Callback(s => positive = true); + mock.When(() => !when).SetupSet(x => x.Value = "foo").Callback(s => negative = true); + + mock.Object.Value = "foo"; + + Assert.True(positive); + Assert.False(negative); + + when = false; + positive = false; + mock.Object.Value = "foo"; + + Assert.False(positive); + Assert.True(negative); + + when = true; + negative = false; + mock.Object.Value = "foo"; + + Assert.True(positive); + Assert.False(negative); + } + + [Fact] + public void ChooseAffirmativeExpectationOnPropertyIndexer() + { + var mock = new Mock(); + + var first = true; + + mock.When(() => first).Setup(x => x[0]).Returns("bar"); + mock.When(() => !first).Setup(x => x[0]).Returns("no bar"); + + Assert.Equal("bar", mock.Object[0]); + first = false; + Assert.Equal("no bar", mock.Object[0]); + first = true; + Assert.Equal("bar", mock.Object[0]); + } + + public interface IFoo + { + string Value { get; set; } + string this[int index] { get; } + void Bar(); + string Foo(); + } + } +} diff --git a/src/Moq.Tests/CustomDefaultValueProviderFixture.cs b/src/Moq.Tests/CustomDefaultValueProviderFixture.cs new file mode 100644 index 000000000..5c3a3db72 --- /dev/null +++ b/src/Moq.Tests/CustomDefaultValueProviderFixture.cs @@ -0,0 +1,133 @@ +// Copyright (c) 2007, Clarius Consulting, Manas Technology Solutions, InSTEDD, and Contributors. +// All rights reserved. Licensed under the BSD 3-Clause License; see License.txt. + +using System; + +using Xunit; + +namespace Moq.Tests +{ + public class CustomDefaultValueProviderFixture + { + [Fact] + public void Custom_DefaultValueProvider_implementations_have_Kind_Custom() + { + var customDefaultValueProvider = new ConstantDefaultValueProvider(null); + + Assert.Equal(DefaultValue.Custom, customDefaultValueProvider.Kind); + } + + [Fact] + public void Custom_DefaultValueProvider_gets_invoked() + { + const int expectedReturnValue = 42; + var constantDefaultValueProvider = new ConstantDefaultValueProvider(expectedReturnValue); + var mock = new Mock() { DefaultValueProvider = constantDefaultValueProvider }; + + var actualReturnValue = mock.Object.GetValue(); + + Assert.Equal(expectedReturnValue, actualReturnValue); + } + + [Fact] + public void Default_values_from_custom_providers_are_not_cached() + { + // NOTE: This specification is not set in stone; it simply documents Moq's behavior at the + // time when custom default value providers became part of the public API. It might very well + // make sense to cache default return values. This could be achieved by turning the purpose- + // bound `Mock.InnerMocks` dictionary into a more generic `Mock.CachedDefaultValues`. + + var mock = new Mock(); + + var values1 = mock.Object.GetValues(); + var values2 = mock.Object.GetValues(); + + Assert.NotSame(values1, values2); + } + + [Fact] + public void Mocks_inherit_custom_default_value_provider_from_MockRepository() + { + var customDefaultValueProvider = new ConstantDefaultValueProvider(null); + var mockRepository = new MockRepository(MockBehavior.Default) { DefaultValueProvider = customDefaultValueProvider }; + + var mock = mockRepository.Create(); + + Assert.Equal(DefaultValue.Custom, mock.DefaultValue); + Assert.Same(customDefaultValueProvider, mock.DefaultValueProvider); + } + + [Fact] + public void Custom_default_value_provider_does_not_interfere_with_recursive_mocking() + { + var nullDefaultValueProvider = new ConstantDefaultValueProvider(null); + var outerMock = new Mock() { DefaultValueProvider = nullDefaultValueProvider }; + + outerMock.Setup(om => om.Inner.GetValue()); + // ^^^^^^ + // If the custom default value provider were used to determine this value, + // then it would be `null` instead of a mocked `IFoo` instance. Multi-dot expressions + // must always use `MockDefaultValueProvider`, regardless of the outermost mock's + // configured default value provider. + var inner = outerMock.Object.Inner; + + Assert.NotNull(inner); + Assert.IsAssignableFrom(inner); + } + + [Fact] + public void Inner_mocks_inherit_custom_default_value_provider_from_outer_mock() + { + const int expectedReturnValue = 42; + var customDefaultValueProvider = new ConstantDefaultValueProvider(expectedReturnValue); + var outerMock = new Mock() { DefaultValueProvider = customDefaultValueProvider }; + + outerMock.Setup(om => om.Inner.GetValues()); // we don't care about GetValues, all we want here is a multi-dot expression + var inner = outerMock.Object.Inner; + var innerMock = Mock.Get(inner); + + Assert.Same(outerMock.DefaultValueProvider, innerMock.DefaultValueProvider); + + var actualReturnValue = inner.GetValue(); + + Assert.Equal(expectedReturnValue, actualReturnValue); + } + + public interface IFoo + { + int Value { get; set; } + int GetValue(); + int[] GetValues(); + IFoo Inner { get; } + + /* Unmerged change from project 'Moq.Tests(net6.0)' + Before: + private sealed class ConstantDefaultValueProvider : DefaultValueProvider + After: + sealed class ConstantDefaultValueProvider : DefaultValueProvider + */ + } + + sealed class ConstantDefaultValueProvider : DefaultValueProvider + + /* Unmerged change from project 'Moq.Tests(net6.0)' + Before: + private object value; + After: + object value; + */ + { + object value; + + public ConstantDefaultValueProvider(object value) + { + this.value = value; + } + + protected internal override object GetDefaultValue(Type type, Mock mock) + { + return this.value; + } + } + } +} diff --git a/src/Moq.Tests/CustomMatcherFixture.cs b/src/Moq.Tests/CustomMatcherFixture.cs new file mode 100644 index 000000000..123e62189 --- /dev/null +++ b/src/Moq.Tests/CustomMatcherFixture.cs @@ -0,0 +1,104 @@ +// Copyright (c) 2007, Clarius Consulting, Manas Technology Solutions, InSTEDD, and Contributors. +// All rights reserved. Licensed under the BSD 3-Clause License; see License.txt. + +using System; + +using Xunit; + +namespace Moq.Tests +{ + public class CustomMatcherFixture + { + [Fact] + public void UsesCustomMatcher() + { + var mock = new Mock(); + + mock.Setup(m => m.Do(Any())).Returns(true); + + Assert.True(mock.Object.Do("foo")); + } + + [Fact] + public void UsesCustomMatcherWithArgument() + { + var mock = new Mock(); + + mock.Setup(m => m.Do(Between(1, 5, Range.Inclusive))).Returns(true); + + Assert.False(mock.Object.Do(6)); + Assert.True(mock.Object.Do(1)); + Assert.True(mock.Object.Do(5)); + } + + public TValue Any() + { + return Match.Create(v => true); + } + + public TValue Between(TValue from, TValue to, Range rangeKind) + where TValue : IComparable + { + return Match.Create(value => + { + if (value == null) + { + return false; + } + + if (rangeKind == Range.Exclusive) + { + return value.CompareTo(from) > 0 && + value.CompareTo(to) < 0; + } + else + { + return value.CompareTo(from) >= 0 && + value.CompareTo(to) <= 0; + } + }); + } + + public interface IFoo + { + bool Do(string value); + bool Do(int value); + } + + [Fact] + public void Custom_matcher_property_appears_by_name_in_verification_error_message() + { + var child = new Mock(); + child.Setup(c => c.PlayWith(Toy.IsRed)).Verifiable(); + + var ex = Assert.Throws(() => child.Verify()); + + Assert.Contains(".PlayWith(CustomMatcherFixture.Toy.IsRed)", ex.Message); + } + + [Fact] + public void Custom_matcher_method_appears_by_name_in_verification_error_message() + { + var child = new Mock(); + child.Setup(c => c.PlayWith(Toy.IsGreen())).Verifiable(); + + var ex = Assert.Throws(() => child.Verify()); + + Assert.Contains(".PlayWith(CustomMatcherFixture.Toy.IsGreen())", ex.Message); + } + + public class Toy + { + public static Toy IsRed => Match.Create((Toy toy) => toy.Color == "red"); + + public static Toy IsGreen() => Match.Create((Toy toy) => toy.Color == "green"); + + public string Color { get; set; } + } + + public interface IChild + { + void PlayWith(Toy toy); + } + } +} diff --git a/src/Moq.Tests/CustomTypeMatchersFixture.cs b/src/Moq.Tests/CustomTypeMatchersFixture.cs new file mode 100644 index 000000000..8966458c1 --- /dev/null +++ b/src/Moq.Tests/CustomTypeMatchersFixture.cs @@ -0,0 +1,325 @@ +// Copyright (c) 2007, Clarius Consulting, Manas Technology Solutions, InSTEDD, and Contributors. +// All rights reserved. Licensed under the BSD 3-Clause License; see License.txt. + +using System; +using System.Collections.Generic; +using System.Linq; + +using Xunit; + +namespace Moq.Tests +{ + public class CustomTypeMatchersFixture + { + [Fact] + public void Setup_with_custom_type_matcher() + { + var invocationCount = 0; + var mock = new Mock(); + mock.Setup(x => x.Method()).Callback(() => invocationCount++); + + mock.Object.Method(); + mock.Object.Method(); + mock.Object.Method(); + mock.Object.Method(); + mock.Object.Method(); + + Assert.Equal(3, invocationCount); + } + + [Fact] + public void Verify_with_custom_type_matcher() + { + var mock = new Mock(); + + mock.Object.Method(); + mock.Object.Method(); + mock.Object.Method(); + mock.Object.Method(); + mock.Object.Method(); + + mock.Verify(x => x.Method(), Times.Exactly(3)); + } + + [Fact] + public void Cannot_use_type_matcher_with_parameterized_constructor_directly_in_Setup() + { + var mock = new Mock(); + + Action setup = () => mock.Setup(x => x.Method()); + + var ex = Assert.Throws(setup); + Assert.Contains("Picky does not have a default (public parameterless) constructor", ex.Message); + } + + [Fact] + public void Cannot_use_nested_type_matcher_with_parameterized_constructor_directly_in_Setup() + { + var mock = new Mock(); + + Action setup = () => mock.Setup(x => x.Method()); + + var ex = Assert.Throws(setup); + Assert.Contains("Picky does not have a default (public parameterless) constructor", ex.Message); + } + + [Fact] + public void Cannot_use_type_matcher_with_parameterized_constructor_directly_in_Verify() + { + var mock = new Mock(); + + Action verify = () => mock.Verify(x => x.Method(), Times.Never); + + var ex = Assert.Throws(verify); + Assert.Contains("Picky does not have a default (public parameterless) constructor", ex.Message); + } + + [Fact] + public void Cannot_use_nested_type_matcher_with_parameterized_constructor_directly_in_Verify() + { + var mock = new Mock(); + + Action verify = () => mock.Verify(x => x.Method(), Times.Never); + + var ex = Assert.Throws(verify); + Assert.Contains("Picky does not have a default (public parameterless) constructor", ex.Message); + } + + [Fact] + public void Can_use_type_matcher_derived_from_one_having_a_parameterized_constructor() + { + var mock = new Mock(); + + mock.Object.Method(); + mock.Object.Method(); + mock.Object.Method(); + mock.Object.Method(); + mock.Object.Method(); + + mock.Verify(x => x.Method(), Times.Exactly(3)); + } + + [Fact] + public void Can_use_struct_type_matchers() + { + var mock = new Mock(); + mock.Setup(m => m.Method()); + } + + [Fact] + public void Must_use_custom_type_matcher_when_type_constraints_present() + { + var mock = new Mock(); + //mock.Setup(y => y.Method()); // wouldn't work because of the type constraint + mock.Setup(y => y.Method()); + + mock.Object.Method(); + + mock.VerifyAll(); + } + + [Fact] + public void Must_use_TypeMatcherAttribute_when_type_constraints_present_that_prevents_direct_implementation_of_ITypeMatcher() + { + var mock = new Mock(); + mock.Setup(z => z.DelegateMethod()); + mock.Setup(z => z.EnumMethod()); + + mock.Object.DelegateMethod(); + mock.Object.EnumMethod(); + + mock.VerifyAll(); + } + + [Fact] + public void It_IsAny_works_with_custom_matcher() + { + var invocationCount = 0; + var mock = new Mock(); + mock.Setup(m => m.Method(It.IsAny())).Callback((object arg) => invocationCount++); + + mock.Object.Method(true); + mock.Object.Method(42); + mock.Object.Method("42"); + mock.Object.Method(new Exception("42")); + mock.Object.Method((string)null); + + Assert.Equal(3, invocationCount); + } + + [Fact] + public void It_IsNotNull_works_with_custom_matcher() + { + var invocationCount = 0; + var mock = new Mock(); + mock.Setup(m => m.Method(It.IsNotNull())).Callback((object arg) => invocationCount++); + + mock.Object.Method(true); + mock.Object.Method(42); + mock.Object.Method("42"); + mock.Object.Method(new Exception("42")); + mock.Object.Method((string)null); + + Assert.Equal(2, invocationCount); + } + + [Fact] + public void It_Is_works_with_custom_matcher() + { + var acceptableArgs = new object[] { 42, "42" }; + + var invocationCount = 0; + var mock = new Mock(); + mock.Setup(m => m.Method(It.Is((arg, _) => acceptableArgs.Contains(arg)))) + .Callback((object arg) => invocationCount++); + + mock.Object.Method(42); + mock.Object.Method(7); + Assert.Equal(1, invocationCount); + + mock.Object.Method("42"); + mock.Object.Method("7"); + mock.Object.Method((string)null); + Assert.Equal(2, invocationCount); + } + + [Fact] + public void It_Is_works_with_custom_comparer() + { + var acceptableArg = "FOO"; + + var invocationCount = 0; + var mock = new Mock(); + mock.Setup(m => m.Method(It.Is(acceptableArg, StringComparer.OrdinalIgnoreCase))) + .Callback((object arg) => invocationCount++); + + mock.Object.Method("foo"); + mock.Object.Method("bar"); + Assert.Equal(1, invocationCount); + + mock.Object.Method("FOO"); + mock.Object.Method("foo"); + mock.Object.Method((string)null); + Assert.Equal(3, invocationCount); + } + + [Fact] + public void It_Is_object_works_with_custom_comparer() + { + var acceptableArg = "FOO"; + + var invocationCount = 0; + var mock = new Mock(); + mock.Setup(m => m.Method(It.Is(acceptableArg, new ObjectStringOrdinalIgnoreCaseComparer()))) + .Callback((object arg) => invocationCount++); + + mock.Object.Method("foo"); + mock.Object.Method("bar"); + Assert.Equal(1, invocationCount); + + mock.Object.Method("FOO"); + mock.Object.Method("foo"); + mock.Object.Method((string)null); + Assert.Equal(3, invocationCount); + } + + public interface IX + { + void Method(); + void Method(T arg); + } + + public interface IY + { + void Method() where TException : Exception; + } + + public interface IZ + { + void DelegateMethod() where TDelegate : Delegate; + void EnumMethod() where TEnum : Enum; + } + + [TypeMatcher] + public sealed class IntOrString : ITypeMatcher + { + public bool Matches(Type typeArgument) + { + return typeArgument == typeof(int) || typeArgument == typeof(string); + } + } + + public sealed class PickyIntOrString : Picky + { + public PickyIntOrString() : base(typeof(int), typeof(string)) + { + } + } + + [TypeMatcher] + public class Picky : ITypeMatcher + + /* Unmerged change from project 'Moq.Tests(net6.0)' + Before: + private readonly Type[] types; + After: + readonly Type[] types; + */ + { + readonly Type[] types; + + public Picky(params Type[] types) + { + this.types = types; + } + + public bool Matches(Type typeArgument) + { + return Array.IndexOf(this.types, typeArgument) >= 0; + } + } + + [TypeMatcher] + public class AnyException : Exception, ITypeMatcher + { + public bool Matches(Type typeArgument) + { + return true; + } + } + + [TypeMatcher(typeof(It.IsAnyType))] + public enum AnyEnum { } + + [TypeMatcher(typeof(It.IsAnyType))] + public delegate void AnyDelegate(); + + [TypeMatcher] + public struct AnyStruct : ITypeMatcher + { + public bool Matches(Type typeArgument) => typeArgument.IsValueType; + } + + public class ObjectStringOrdinalIgnoreCaseComparer : IEqualityComparer + + /* Unmerged change from project 'Moq.Tests(net6.0)' + Before: + private static IEqualityComparer InternalComparer => StringComparer.OrdinalIgnoreCase; + After: + static IEqualityComparer InternalComparer => StringComparer.OrdinalIgnoreCase; + */ + { + static IEqualityComparer InternalComparer => StringComparer.OrdinalIgnoreCase; + + public new bool Equals(object x, object y) + { + return InternalComparer.Equals((string)x, (string)y); + } + + public int GetHashCode(object obj) + { + return InternalComparer.GetHashCode((string)obj); + } + } + } +} diff --git a/src/Moq.Tests/DefaultValueProviderFixture.cs b/src/Moq.Tests/DefaultValueProviderFixture.cs new file mode 100644 index 000000000..8a1ca3e7e --- /dev/null +++ b/src/Moq.Tests/DefaultValueProviderFixture.cs @@ -0,0 +1,94 @@ +// Copyright (c) 2007, Clarius Consulting, Manas Technology Solutions, InSTEDD, and Contributors. +// All rights reserved. Licensed under the BSD 3-Clause License; see License.txt. + +using System; +using System.Reflection; + +using Xunit; + +namespace Moq.Tests +{ + /// + /// Tests for the abstract base class. + /// + public class DefaultValueProviderFixture + + /* Unmerged change from project 'Moq.Tests(net6.0)' + Before: + private static MethodInfo fooActionMethod = typeof(IFoo).GetMethod(nameof(IFoo.Action)); + private static ParameterInfo fooActionMethodParameter = typeof(IFoo).GetMethod(nameof(IFoo.Action)).GetParameters()[0]; + private static MethodInfo fooFuncMethod = typeof(IFoo).GetMethod(nameof(IFoo.Func)); + After: + static MethodInfo fooActionMethod = typeof(IFoo).GetMethod(nameof(IFoo.Action)); + static ParameterInfo fooActionMethodParameter = typeof(IFoo).GetMethod(nameof(IFoo.Action)).GetParameters()[0]; + static MethodInfo fooFuncMethod = typeof(IFoo).GetMethod(nameof(IFoo.Func)); + */ + { + static MethodInfo fooActionMethod = typeof(IFoo).GetMethod(nameof(IFoo.Action)); + static ParameterInfo fooActionMethodParameter = typeof(IFoo).GetMethod(nameof(IFoo.Action)).GetParameters()[0]; + static MethodInfo fooFuncMethod = typeof(IFoo).GetMethod(nameof(IFoo.Func)); + + + /* Unmerged change from project 'Moq.Tests(net6.0)' + Before: + private DefaultValueProvider defaultValueProvider; + private Mock fooMock; + After: + DefaultValueProvider defaultValueProvider; + Mock fooMock; + */ + DefaultValueProvider defaultValueProvider; + Mock fooMock; + + public DefaultValueProviderFixture() + { + this.defaultValueProvider = new DefaultValueProviderStub(); + this.fooMock = new Mock(); + } + + [Fact] + public void GetDefaultParameterValue_returns_same_value_as_GetDefaultValue_if_GetDefaultParameterValue_not_overridden() + { + var _ = this.fooMock; + var parameter = fooActionMethodParameter; + var expected = this.defaultValueProvider.GetDefaultValue(parameter.ParameterType, _); + + var actual = this.defaultValueProvider.GetDefaultParameterValue(parameter, _); + + Assert.Equal(expected, actual); + } + + [Fact] + public void GetDefaultReturnValue_returns_same_value_as_GetDefaultValue_if_GetDefaultReturnValue_not_overridden() + { + var _ = this.fooMock; + var method = fooFuncMethod; + var expected = this.defaultValueProvider.GetDefaultValue(method.ReturnType, _); + + var actual = this.defaultValueProvider.GetDefaultReturnValue(method, _); + + Assert.Equal(expected, actual); + } + + public interface IFoo + { + void Action(object arg); + object Func(); + + /* Unmerged change from project 'Moq.Tests(net6.0)' + Before: + private sealed class DefaultValueProviderStub : DefaultValueProvider + After: + sealed class DefaultValueProviderStub : DefaultValueProvider + */ + } + + sealed class DefaultValueProviderStub : DefaultValueProvider + { + protected internal override object GetDefaultValue(Type type, Mock mock) + { + return 42; + } + } + } +} diff --git a/src/Moq.Tests/Demo.cs b/src/Moq.Tests/Demo.cs new file mode 100644 index 000000000..49b3fc4ed --- /dev/null +++ b/src/Moq.Tests/Demo.cs @@ -0,0 +1,136 @@ +// Copyright (c) 2007, Clarius Consulting, Manas Technology Solutions, InSTEDD, and Contributors. +// All rights reserved. Licensed under the BSD 3-Clause License; see License.txt. + +using System; + +using Xunit; + +namespace Moq.Tests +{ + public class Demo + + /* Unmerged change from project 'Moq.Tests(net6.0)' + Before: + private static string TALISKER = "Talisker"; + After: + static string TALISKER = "Talisker"; + */ + { + static string TALISKER = "Talisker"; + + [Fact] + public void FillingRemovesInventoryIfInStock() + { + //setup - data + var order = new Order(TALISKER, 50); + var mock = new Mock(); + + //setup - expectations + mock.Setup(x => x.HasInventory(TALISKER, 50)).Returns(true); + + //exercise + order.Fill(mock.Object); + + //verify state + Assert.True(order.IsFilled); + //verify interaction + mock.VerifyAll(); + } + + [Fact] + public void FillingDoesNotRemoveIfNotEnoughInStock() + { + //setup - data + var order = new Order(TALISKER, 50); + var mock = new Mock(); + + //setup - expectations + mock.Setup(x => x.HasInventory(It.IsAny(), It.IsInRange(0, 100, Range.Inclusive))).Returns(false); + mock.Setup(x => x.Remove(It.IsAny(), It.IsAny())).Throws(new InvalidOperationException()); + + //exercise + order.Fill(mock.Object); + + //verify + Assert.False(order.IsFilled); + } + + [Fact] + public void TestPresenterSelection() + { + var mockView = new Mock(); + var presenter = new OrdersPresenter(mockView.Object); + + // Check that the presenter has no selection by default + Assert.Null(presenter.SelectedOrder); + + // Finally raise the event with a specific arguments data + mockView.Raise(mv => mv.OrderSelected += null, new OrderEventArgs { Order = new Order("moq", 500) }); + + // Now the presenter reacted to the event, and we have a selected order + Assert.NotNull(presenter.SelectedOrder); + Assert.Equal("moq", presenter.SelectedOrder.ProductName); + } + + public class OrderEventArgs : EventArgs + { + public Order Order { get; set; } + } + + public interface IOrdersView + { + event EventHandler OrderSelected; + } + + public class OrdersPresenter + { + public OrdersPresenter(IOrdersView view) + { + view.OrderSelected += (sender, args) => DoOrderSelection(args.Order); + } + + public Order SelectedOrder { get; private set; } + + + /* Unmerged change from project 'Moq.Tests(net6.0)' + Before: + private void DoOrderSelection(Order selectedOrder) + After: + void DoOrderSelection(Order selectedOrder) + */ + void DoOrderSelection(Order selectedOrder) + { + // Do something when the view selects an order. + SelectedOrder = selectedOrder; + } + } + + public interface IWarehouse + { + bool HasInventory(string productName, int quantity); + void Remove(string productName, int quantity); + } + + public class Order + { + public string ProductName { get; private set; } + public int Quantity { get; private set; } + public bool IsFilled { get; private set; } + + public Order(string productName, int quantity) + { + this.ProductName = productName; + this.Quantity = quantity; + } + + public void Fill(IWarehouse warehouse) + { + if (warehouse.HasInventory(ProductName, Quantity)) + { + warehouse.Remove(ProductName, Quantity); + IsFilled = true; + } + } + } + } +} diff --git a/src/Moq.Tests/EmptyDefaultValueProviderFixture.cs b/src/Moq.Tests/EmptyDefaultValueProviderFixture.cs new file mode 100644 index 000000000..5a4c41643 --- /dev/null +++ b/src/Moq.Tests/EmptyDefaultValueProviderFixture.cs @@ -0,0 +1,231 @@ +// Copyright (c) 2007, Clarius Consulting, Manas Technology Solutions, InSTEDD, and Contributors. +// All rights reserved. Licensed under the BSD 3-Clause License; see License.txt. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +using Xunit; + +namespace Moq.Tests +{ + public class EmptyDefaultValueProviderFixture + { + [Fact] + public void ProvidesNullString() + { + var value = GetDefaultValueForProperty(nameof(IFoo.StringValue)); + + Assert.Null(value); + } + + [Fact] + public void ProvidesDefaultInt() + { + var value = GetDefaultValueForProperty(nameof(IFoo.IntValue)); + + Assert.Equal(default(int), value); + } + + [Fact] + public void ProvidesNullInt() + { + var value = GetDefaultValueForProperty(nameof(IFoo.NullableIntValue)); + + Assert.Null(value); + } + + [Fact] + public void ProvidesDefaultBool() + { + var value = GetDefaultValueForProperty(nameof(IFoo.BoolValue)); + + Assert.Equal(default(bool), value); + } + + [Fact] + public void ProvidesDefaultEnum() + { + var value = GetDefaultValueForProperty(nameof(IFoo.UriKind)); + + Assert.Equal(default(UriKind), value); + } + + [Fact] + public void ProvidesEmptyEnumerable() + { + var value = GetDefaultValueForProperty(nameof(IFoo.Indexes)); + Assert.True(value is IEnumerable && ((IEnumerable)value).Count() == 0); + } + + [Fact] + public void ProvidesEmptyArray() + { + var value = GetDefaultValueForProperty(nameof(IFoo.Bars)); + Assert.True(value is IBar[] && ((IBar[])value).Length == 0); + } + + [Fact] + public void ProvidesNullReferenceTypes() + { + var value1 = GetDefaultValueForProperty(nameof(IFoo.Bar)); + var value2 = GetDefaultValueForProperty(nameof(IFoo.Object)); + + Assert.Null(value1); + Assert.Null(value2); + } + + [Fact] + public void ProvideEmptyQueryable() + { + var value = GetDefaultValueForProperty(nameof(IFoo.Queryable)); + + Assert.IsAssignableFrom>(value); + Assert.Equal(0, ((IQueryable)value).Count()); + } + + [Fact] + public void ProvideEmptyQueryableObjects() + { + var value = GetDefaultValueForProperty(nameof(IFoo.QueryableObjects)); + + Assert.IsAssignableFrom(value); + Assert.Equal(0, ((IQueryable)value).Cast().Count()); + } + + [Fact] + public void ProvidesDefaultTask() + { + var value = GetDefaultValueForProperty(nameof(IFoo.TaskValue)); + + Assert.NotNull(value); + Assert.True(((Task)value).IsCompleted); + } + + [Fact] + public void ProvidesDefaultGenericTaskOfValueType() + { + var value = GetDefaultValueForProperty(nameof(IFoo.GenericTaskOfValueType)); + + Assert.NotNull(value); + Assert.True(((Task)value).IsCompleted); + Assert.Equal(default(int), ((Task)value).Result); + } + + [Fact] + public void ProvidesDefaultGenericTaskOfReferenceType() + { + var value = GetDefaultValueForProperty(nameof(IFoo.GenericTaskOfReferenceType)); + + Assert.NotNull(value); + Assert.True(((Task)value).IsCompleted); + Assert.Equal(default(string), ((Task)value).Result); + } + + [Fact] + public void ProvidesDefaultTaskOfGenericTask() + { + var value = GetDefaultValueForProperty(nameof(IFoo.TaskOfGenericTaskOfValueType)); + + Assert.NotNull(value); + Assert.True(((Task)value).IsCompleted); + Assert.Equal(default(int), ((Task>)value).Result.Result); + } + + [Fact] + public void ProvidesDefaultValueTaskOfValueType() + { + var value = GetDefaultValueForProperty(nameof(IFoo.ValueTaskOfValueType)); + + var result = (ValueTask)value; + Assert.True(result.IsCompleted); + Assert.Equal(default(int), result.Result); + } + + [Fact] + public void ProvidesDefaultValueTaskOfValueTypeArray() + { + var value = GetDefaultValueForProperty(nameof(IFoo.ValueTaskOfValueTypeArray)); + + var result = (ValueTask)value; + Assert.True(result.IsCompleted); + Assert.NotNull(result.Result); + Assert.Empty(result.Result); + } + + [Fact] + public void ProvidesDefaultValueTaskOfReferenceType() + { + var value = GetDefaultValueForProperty(nameof(IFoo.ValueTaskOfReferenceType)); + + var result = (ValueTask)value; + Assert.True(result.IsCompleted); + Assert.Equal(default(string), result.Result); + } + + [Fact] + public void ProvidesDefaultValueTaskOfTaskOfValueType() + { + var value = GetDefaultValueForProperty(nameof(IFoo.ValueTaskOfTaskOfValueType)); + + var result = (ValueTask>)value; + Assert.True(result.IsCompleted); + Assert.NotNull(result.Result); + Assert.True(result.Result.IsCompleted); + Assert.Equal(default(int), result.Result.Result); + } + + [Fact] + public void ProvidesDefaultValueTupleOfReferenceTypeArrayAndTaskOfReferenceType() + { + var value = GetDefaultValueForProperty(nameof(IFoo.ValueTupleOfReferenceTypeArrayAndTaskOfReferenceType)); + + var (bars, barTask) = ((IBar[], Task))value; + Assert.NotNull(bars); + Assert.Empty(bars); + Assert.NotNull(barTask); + Assert.True(barTask.IsCompleted); + Assert.Equal(default(IBar), barTask.Result); + + /* Unmerged change from project 'Moq.Tests(net6.0)' + Before: + private static object GetDefaultValueForProperty(string propertyName) + After: + static object GetDefaultValueForProperty(string propertyName) + */ + } + + static object GetDefaultValueForProperty(string propertyName) + { + var propertyGetter = typeof(IFoo).GetProperty(propertyName).GetGetMethod(); + return DefaultValueProvider.Empty.GetDefaultReturnValue(propertyGetter, new Mock()); + } + + public interface IFoo + { + object Object { get; set; } + IBar Bar { get; set; } + string StringValue { get; set; } + int IntValue { get; set; } + bool BoolValue { get; set; } + int? NullableIntValue { get; set; } + UriKind UriKind { get; set; } + IEnumerable Indexes { get; set; } + IBar[] Bars { get; set; } + IQueryable Queryable { get; } + IQueryable QueryableObjects { get; } + Task TaskValue { get; set; } + Task GenericTaskOfValueType { get; set; } + Task GenericTaskOfReferenceType { get; set; } + Task> TaskOfGenericTaskOfValueType { get; set; } + ValueTask ValueTaskOfValueType { get; set; } + ValueTask ValueTaskOfValueTypeArray { get; set; } + ValueTask ValueTaskOfReferenceType { get; set; } + ValueTask> ValueTaskOfTaskOfValueType { get; set; } + (IBar[], Task) ValueTupleOfReferenceTypeArrayAndTaskOfReferenceType { get; } + } + + public interface IBar { } + } +} diff --git a/src/Moq.Tests/EventHandlerTypesMustMatchFixture.cs b/src/Moq.Tests/EventHandlerTypesMustMatchFixture.cs new file mode 100644 index 000000000..413a12bec --- /dev/null +++ b/src/Moq.Tests/EventHandlerTypesMustMatchFixture.cs @@ -0,0 +1,71 @@ +ο»Ώ// Copyright (c) 2007, Clarius Consulting, Manas Technology Solutions, InSTEDD, and Contributors. +// All rights reserved. Licensed under the BSD 3-Clause License; see License.txt. + +using System; + +using Xunit; + +namespace Moq.Tests +{ + public class EventHandlerTypesMustMatchFixture + { + [Fact] + public void CLI_requires_event_handlers_to_have_the_exact_same_type() + { + var mouse = new Mouse(); + var result = 2; + + mouse.LeftButtonClicked += new Action(_ => result += 3); + mouse.LeftButtonClicked += new Action(_ => result *= 4); + mouse.RaiseLeftButtonClicked(new LeftButton()); + + Assert.Equal(20, result); + } + + [Fact] + public void CLI_throws_if_event_handlers_do_not_have_the_exact_same_type() + { + var mouse = new Mouse(); + mouse.LeftButtonClicked += new Action